I have moved!

I've moved my blog
CLICK HERE

Thursday 29 January 2009

Using Collection Initializers with Immutable Lists

The Incomparable Mr Skeet mentions how irksome it is that collection initializers assume that the collection is mutable.

Suppose we defined an "immutable-yet-initializable collection" as any type that supports this interface:

public interface IImmutableCollection<TCollection, TItem> : IEnumerable<TItem>
       where TCollection : IImmutableCollection<TCollection, TItem>, new()
{
    TCollection Add(TItem item);
}

In other words, it can be enumerated, it can be default-constructed (to get an empty list) and it has an Add method that returns a new list with the item added to it.

And suppose our simple demo implementation of that is as follows:

public class SimpleImmutableList<TItem> : 
             IImmutableCollection<SimpleImmutableList<TItem>, TItem>
{
    private SimpleImmutableList<TItem> _next;

    private TItem _head;

    public SimpleImmutableList<TItem> Add(TItem item)
    {
        return new SimpleImmutableList<TItem>
                   {
                       _next = this,
                       _head = item
                   };
    }

    public IEnumerator<TItem> GetEnumerator()
    {
        for (SimpleImmutableList<TItem> i = this; 
             i._next != null; i = i._next)
            yield return i._head;
    }

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

Not very sophisticated, doesn't attempt to solve the well-known "OMG! my list is backward" problem. But anyway. Our problem is that we want this delightful experience:

List<string> mutableList = 
        new List<string>
                   {
                       "First",
                       "Second",
                       "Third"
                   };
Debug.Assert(mutableList.Count() == 3);
Debug.Assert(mutableList.ElementAt(2) == "Third");

Except that we want to make a SimpleImmutableList<string> instead of a List<string>. Well, we can get pretty close:

SimpleImmutableList<string> immutableList = 
        new Initializer<SimpleImmutableList<string>, string>
                    {
                        "First",
                        "Second",
                        "Third"
                    }.Result;
Debug.Assert(immutableList.Count() == 3);
Debug.Assert(immutableList.ElementAt(2) == "First"); // OMG! etc

I've picked out in red ink the extra noise we have to add, but it's not that bad. The enabling thing is that Initializer class:

public class Initializer<TCollection, TItem>  : IEnumerable<TItem>
       where TCollection : IImmutableCollection<TCollection, TItem>, 
                           new()
{
    public Initializer()
    {
        Result = new TCollection();
    }

    public void Add(TItem item)
    {
        Result = Result.Add(item);
    }

    public TCollection Result { get; private set; }

    public IEnumerator<TItem> GetEnumerator()
    {
        return Result.GetEnumerator();
    }

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

So if anyone tells you that it is impossible to initialize an immutable collection with a collection initializer, tell them about the time you met a crazy old man with some wild ideas about an Initializer class, and see what they say1.

1. "Get out of my house".

No comments: