Cook Computing

More on Collection Initializers

December 3, 2011 Written by Charles Cook

While looking at collection initializers I came across mucking about with hashes... and more mucking about with hashes by Alex Henderson (via Phil Haack). In these posts he describes ways of creating an instance of a string-keyed Dictionary in a Ruby-like way using lambda expressions, for example:

Dictionary<string, string> items = Hash(Age => "10", Height => "20");

The second post contains the fastest implementation (contributed by Andrey Shcekin):

public Dictionary<string, T> Hash<T>(params Func<string, T>[] args)
where T : class  
{
    var items = new Dictionary<string, T>();
    foreach (var func in args)
    {
        var item = func(null);
        items.Add(func.Method.GetParameters()[0].Name, item);
    }
    return items;
}

[Comment: I don't see why the class constraint is necessary]

This led me to think that I could implement an Add extension method which would allow a collection initializer to be used, for example like this:

var items = new Dictionary<string, string> {  Name => "alex",  Age => "10" };

I came up with this:

static class Extensions
{
  public static void Add<T>(this Dictionary<string, T> dict, Func<string, T> args)
  {
    var item = args(null);
    dict.Add(args.Method.GetParameters()[0].Name, item);
  }
}

Unfortunately, although this works fine when Add is invoked explicitly, it fails to compile when attempting to use it in a collection initializer. The compiler only recognizes member functions called Add, not extension methods. C# PM Alex Turner wrote on Connect:

You're right that the spec is ambiguous here! There was no explicit decision in C# 3.0 to prevent this from working; it was simply an implementation limitation we accepted when we discovered it late in the product cycle. We see no design reason not to add this now, but unfortunately, we are starting to lock down on our feature set and won't be able to get to this feature for this release. We'll definitely keep this in mind when planning for our next release!

...I've added a vote for collection initializers binding to Add extension methods to the OneNote notebook we use internally to track C# compiler and language requests. We can't promise if or when we'll get to this feature, but we'll definitely keep it in mind during planning for the next release!

I tried this with the .NET 4.5 Developer Preview and it still fails to build, but of course this may change before the final release.

On the other hand, VB does support the use of extension methods called Add in collection initializers but its syntax for lambda expressions rather misses the point of the exercise:

Imports System.Runtime.CompilerServices

Module Sample
    Sub Main()
        Dim dict As New Dictionary(Of String, Integer) _
            From { Function(Age As String) 10, Function(Height As String) 20 }
    End Sub

    <Extension()>
    Sub Add(Of T)(ByVal dict As Dictionary(Of String, T), 
                  ByVal args As Func(Of String, T))
        Dim item As T
        item = args(Nothing)
        dict.Add(args.Method.GetParameters()(0).Name, item)
    End Sub
End Module