views:

302

answers:

2

You know probably the following problem: You want to share a collection between two objects A and B (or similarly want to expose a collection by a property) and ... well, you realize that this is actually not a good idea since both A and B can then modify the collection and blah blah armageddon now ...

But often you realize that you don't want to share the collection as a whole but only want to initialize an object with the items of the collection or pass the items to a method of the object. Furthermore the items of the object don't change during the lifetime of an object, so you often end up doing things like this:

public class Foo
{
  private List<int> items;

  public Foo(IEnumerable<int> items)
  {
    this.items = items.ToList();
  }

  public ReadOnlyCollection<int> Items
  {
    get { return new ReadOnlyCollection(this.items); }
  }
}

Both .ToList() and the ReadonlyCollection-Wrapper provide a reasonable solution to the problem but the also suffer from some problems: .ToList() copies the whole List - so I end up having x instances of the same collection while the ReadOnlyCollection only ensures that clients don't change the collection but don't give any guarantee to the clients that the collection won't change.

Actually this is a situation I am quite often confronted with because most of the classes I write (guessing: 80 - 90 %) are effectively immutable and aggregate often collections which basically represent immutable arrays (meaning in this context: a fixed-size immutable sequence of items). From my point of view a good solution to this problem is quite simple: Build a wrapper class for an array which is guaranteed to be initialized properly in the constructor and provide only methods/properties that don't change the underlying array.

Long story short: Is there anything wrong with the following (trivial) implementation of the concept of an immutable array?

Btw: I decided that Sequence is an appropriate name since a) it describes the intention quite good (a fixed-size immutable sequence of items) b) it is not a name currently used by .net and c) it is very short which is important because I am planning to use it extensively.

public sealed class Sequence<T> : IEnumerable<T>
    {
     private readonly T[] items;

     public Sequence(IEnumerable<T> items)
     {
      this.items = items.ToArray();
     }

     public Sequence(int size, Func<int, T> producer)
     {
      this.items = new T[size];

      for (int i = 0; i < size; i++)
      {
       this.items[i] = producer(i);
      }
     }

     public T this[int i]
     {
      get { return this.items[i]; }
     }

     public int Length
     {
      get { return this.items.Length; }
     }

     public bool Contains(T item)
     {
      for (int i = 0; i < this.items.Length; i++)
      {
       if (this.items[i].Equals(item))
        return true;
      }

      return false;
     }

     public Enumerator<T> GetEnumerator()
     {
      return new Enumerator<T>(this);
     }

     IEnumerator<T> System.Collections.Generic.IEnumerable<T>.GetEnumerator()
     {
      return GetEnumerator();
     }

     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
     {
      return GetEnumerator();
     }

     public struct Enumerator<T> : IEnumerator<T>
     {
      private T[] items;
      private int nextIndex;
      private T current;

      internal Enumerator(Sequence<T> immutableArray)
      {
       this.items = immutableArray.items;
       this.nextIndex = 0;
       this.current = default(T);
      }

      public T Current
      {
       get { return this.current; }
      }

      object System.Collections.IEnumerator.Current
      {
       get { return this.Current; }
      }

      public void Dispose()
      {
      }

      public bool MoveNext()
      {
       if (this.nextIndex < this.items.Length)
       {
        this.current = this.items[this.nextIndex];
        this.nextIndex++;
        return true;
       }
       else
       {
        return false;
       }
      }

      public void Reset()
      {
       this.nextIndex = 0;
       this.current = default(T);
      }
     }
    }
+2  A: 

Seems okay. Note that you could compress the whole IEnumerable-Code to a few lines by using yield return or just returning Items.GetEnumerator().

Dario
Yeah, thats true but I just want to do it properly and ensure that the enumerating is as fast as possible ;)
Bietchiebatchie
It's easier to check whether an implementation is done properly if you used yield because then the compiler would have to do the dirty work ;-)
Dario
+2  A: 

What would your Sequence class give you that ReadOnlyCollection doesn't? Couldn't you just use simple inheritance to provide the constructors that you need?

public class Sequence<T> : ReadOnlyCollection<T>
{
    public Sequence(IEnumerable<T> items)
        : base(items.ToList()) { }

    public Sequence(int size, Func<int, T> generator)
        : base(Enumerable.Range(0, size).Select(generator).ToList()) { }

    // properties, methods etc provided by the ReadOnlyCollection base class
}
LukeH
The Sequence class doesn't provide anything new compared to the ReadonlyCollection class - actually it provides even less. But thats the point: Half of the implemented methods/properties of IList/ICollection throw exceptions when invoked. And while most other Collections classes in .net are very good designed; in this special case I don't like the desing at all.
Bietchiebatchie
@Frederik: All the properties/methods that would throw have an explicit interface implementation, so they're essentially invisible unless you cast your collection to the relevant interface (`IList`, `ICollection` etc).
LukeH