C#中的不可变集合支持(包含类似字符串)方法,例如“Contains(IEnumerable <t>)”?</t>

时间:2012-08-13 21:00:35

标签: c# string collections immutability

String类表示“字符集合”并且是不可变的。它的索引器只定义了函数,因为Char“struct”也是不可变的。用于操作的所有String方法都返回String类的新实例。

我最近需要一个不可变的泛型集合,其完全就像String一样(我们称之为Foo<T>)。

  • 它应该是泛型(虽然我只会将它与结构一起使用)。
  • 应该不可变
  • 它应该具有序列项的方法,例如:
    • IndexOf(Foo<T>)IndexOf(IEnumerable<T>)
    • StartsWith(Foo<T>)StartsWith(IEnumerable<T>)
    • EndsWith(Foo<T>)EndsWith(IEnumerable<T>)
    • Take(int, int)(包括起始索引和长度,就像Substring
    • Contains(Foo<T>)Contains(IEnumerable<T>)
    • LastIndexOf(Foo<T>)LastIndexOf(IEnumerable<T>)

我创建了一个不可变类,用于对其项目进行只读访问,并编写了一些扩展方法来模仿String的功能,但我确实怀疑我的实现效率(我实际上要求Replace方法,{{3 }})。我对替代品感到好奇。由于String完成了我需要的一切(不幸的是只有chars),感觉就像重新发明轮子一样。

我需要的最简单的定义是“一般字符串”。

  • .NET中是否有这样的东西或者是为.NET编写的?
  • 如果没有,创建一个指南会很棒。

在回答和评论后编辑:

我需要的是一个包装指定的底层可变集合并将其表示为只读的包装器。我需要的是一个真正不可变的T集合,其中包含处理<{>} T 序列的方法。以IList<T>.IndexOf(T)为例,它获取的索引。现在考虑String.IndexOf(String)方法,它(与IndexOf(Char)的{​​{1}}方法不同)得到字符序列的索引,而String有很多这些方法。

现在,为什么我不使用String:除了它不支持“(类似于字符串)的方法,如Contains(IEnumerable)”,它也是不可变。一个例子:

ReadOnlyCollection<T>

无法(here)更改字符串的状态。现在,我们来看看var array = new char[] { 'a', 'b', 'c', 'd', 'e' }; var str = new string(array); // array[2] is 'c' and str[2] is also 'c' // I can't do str[2] = 'f', but: array[2] = 'f'; // Now, array[2] is 'f' but str[2] is still 'c'

ReadOnlyCollection<T>

根据请求进行编辑 - 我目前使用的是:

集合(var array = new int[] { 1, 2, 3, 4, 5 }; var col = new ReadOnlyCollection<int>(array); // Here the col[2] is 3 // I can't do col[2] = 6, but: array[2] = 6; // Now the col[2] is 6 as well. ):

Foo<T>

扩展方法:

// Something I started like an hour ago. The only thing it does right now is to
// copy (not wrap) a specified enumerable and provide read-only access to it.
public sealed class Foo<T> : IList<T> where T: struct
{
    private readonly T[] _Array;

    public T this[int index] { get { return _Array[index]; } }
    IList<T>.this[int index]
    {
        get { return this[index]; }
        set { throw new NotSupportedException(); }
    }
    public Foo(IEnumerable<T> collection)
    {
        // Enumerable.ToArray() method copies the content of the specified array.
        // Whetever happens to the "collection", value of "_Array" will stay the same. 
        _Array = collection.ToArray();
    }

    // Most of the methods of IList<T> are explicitly implemented. IsReadOnly
    // returns true and the methods that cause a change in collection throw
    // "NotSupportedException"s just like ReadOnlyCollection<T>.
    // IEnumerable<T> implementation uses an iterator block.
}

4 个答案:

答案 0 :(得分:1)

这里似乎有两个问题:

1)创建一个不可变的集合

简短的回答是,没有内置的支持。

最接近的答案实际上是ReadOnlyCollection,你可以创建一个简单的包装器

public class ImmutableCollection<T> : ReadOnlyCollection<T> {

  public ImmutableCollection(IEnumerable<T> source) : base(source.ToList()) {}

}

构造函数中的ToList调用会生成源集合的副本,以便您可以修改源集合

如果不这样做,您将不得不实施自己的类,可能继承自IList<T>IEnumerable<T>并提供您自己的访问权限。

无论哪种方式,您都必须记住,每个T都不能保证是不可变的(即使是结构,因为结构可能有一个作为参考对象的字段成员)。

但是,由于您仍然需要复制源集合以使您的集合可以变化,因此使用第一个示例仍然会更好。

2)提供额外的功能来执行类似字符串的操作。

您必须实现自己的功能:

  public bool Contains(IEnumerable<T> pattern) {
   return IndicesOf(pattern).Any();
 }           

 public int IndexOf(IEnumerable<T> pattern) {
   return IndicesOf(pattern).Select(x=>(int?)x).FirstOrDefault() ?? -1;
 }           

 public int LastIndexOf(IEnumerable<T> pattern) {
   return IndicesOf(pattern).Select(x=>(int?)x).LastOrDefault()?? -1;
 }           

 public IEnumerable<int> IndicesOf(IEnumerable <T> pattern) {
  var count=pattern.Count();
  return Enumerable.Range(0,this.Count()-count).Where(i=> pattern.SequenceEqual(internalTake(i,count)));
 }           

 public IEnumerable<int> LastIndicesOf(IEnumerable<T> pattern) {
   return IndicesOf(pattern).Reverse(); // Could Optimize
 }

 private IEnumerable<IEnumerable<T>> internalSplit(IEnumerable<T> seperator) {
   var splitPoints=this.IndicesOf(seperator);
   var length=seperator.Count();
   var lastCount=0;
   foreach(var point in splitPoints.Where(x=>!splitPoints.Any(y=>y<x && y+length>x))) {
        yield return this.Take(lastCount,point-lastCount);
        lastCount=point+length;
   }
   yield return this.TakeAll(lastCount);
 } 


 public ImmutableCollection<T>[] Split(IEnumerable<T> seperator) {
   return internalSplit(seperator).Select(x=>new ImmutableCollection<T>(x)).ToArray();
 }          

 public bool StartsWith(IEnumerable<T> pattern) {
    return pattern.SequenceEqual(this.Take(pattern.Count()));
 }           
 public bool EndsWith(IEnumerable<T> pattern) {
    return pattern.SequenceEqual(this.Skip(this.Count()-pattern.Count()));
 }           

 private IEnumerable<T> internalTake(int startIndex, int length) {
    var max=(length==-1) ? this.Count() : Math.Min(this.Count(),startIndex+length);
    for (int i=startIndex;i<max;i++) yield return this[i];
 }

 public ImmutableCollection<T> Take(int startIndex, int length) {
    return new ImmutableCollection<T>(internalTake(startIndex,length));
 }           

 public ImmutableCollection<T> TakeAll(int startIndex) {
    return new ImmutableCollection<T>(internalTake(startIndex,-1));
 }           

答案 1 :(得分:0)

我认为这就是你要找的东西:List.AsReadOnly http://msdn.microsoft.com/en-us/library/e78dcd75.aspx

答案 2 :(得分:0)

如果你想找到一个有效的方法来找到一个子序列,你最好的可能就是为此目的编写你自己的收藏。我建议使用T[]结合int[]来存储每个值的哈希码。然后,可以在很大程度上减少查找T序列以查找哈希值序列的任务。可以使用3个字符将N个哈希码序列转换为3N字符串来存储每个哈希码,然后使用string.Contains或正则表达式解析器来执行序列查找。

答案 3 :(得分:-1)