Collection initializers and Add method with more than one parameter

Collection initializers and Add method with more than one parameter

Rate This
  • Comments 7

I didn’t know this about C# until today. This code compiles:

using System;
using System.Collections.Generic;
 
class List : List<Tuple<string, int, bool>>
{
    public void Add(string s, int i, bool b)
    {
        Add(Tuple.Create(s, i, b));
    }
 
    static void Main(string[] args)
    {
        var list = new List
        {
            { "zero", 0, true },
            { "one", 1, false }
        };
 
        foreach (var item in list)
        {
            Console.WriteLine(item.ToString());
        }
    }
}

and outputs:

(zero, 0, True)
(one, 1, False)

It turns out, the way collection initializers work they pattern match to expect the Add method on the list object, and it can have more than one or two parameters. This is extremely handy for writing declarative, data-driven unit-tests where you only specify the data for your scenario and the imperative control-flow of the test is abstracted away into a helper method. This allows you to shape the relevant data in a way convenient to be able to clearly read the test scenario.

P.S. For a second, I admit to having a thought “I bet even Jon Skeet didn’t know about this one”, but then of course I opened C# in Depth 2nd Edition, page 216 and was once again in awe of Jon’s superpowers. Sorry for ever having the slightest of doubt, Jon, won’t happen again!

  • WRT data-driven unit tests, some links for others that may be interested.  Most of the frameworks seem to support both 'inline' data (declared within the source) and having it pulled from an external source (excel, database, etc)

    - Parameterized unit tests in NUnit @ nunit.org/index.php

    - Theory tests in xunit @ xunit.codeplex.com/wikipage

    - data-driven in mstest @ msdn.microsoft.com/.../hh694602.aspx

    If you can use your test framework's support and your test runner has support for it as well, it's a little nicer since they can be treated as N different tests in the results (giving you the additional details of which passed and which failed) instead of just "one of them failed" you get when doing it 'manually' in a single test :)

  • sorry, should have included this in the last comment :(

    One usage scenario I see some people miss out on using is that you can use collection initializers inside property initializers even if you're not declaring a new instance.

    In many scenarios, objects are created with their collection properties already initialized (they start out as an empty collection instead of being null).  This doesn't mean you have to manually call foo.Bar.Add after creating foo, though :)

    void Main()

    {

       var foo = new HasPreInitializedCollectionProperties()

       {

           SomeDictionary =

           {

               { 123, "foo" },

               { 456, "bar" },

           },

           SomeList =

           {

               "foo",

               "bar",

               "baz",

           }

       };

       foo.Dump();

    }

    public class HasPreInitializedCollectionProperties

    {

       private readonly Dictionary<int, string> _dictionary = new Dictionary<int, string>();

       public Dictionary<int, string> SomeDictionary { get { return _dictionary; } }

       private readonly List<string> _list = new List<string>();

       public List<string> SomeList { get { return _list; } }

    }

  • This also works with more sophisticated collections, like Dictionary. It helps prevent code like this:

    string longName = string.Empty;

    switch (shortName)

    {

       case "pt1":

           longName = "Part 1";

           break;

       case "pt2":

           longName = "Part 2";

           break;

       //....

       default:

           break;

    }

  • Unfortunately this feature has one huge drawback: it does not work with extension methods. Without it, one must create custom collection classes, whereas the true power of this would be to use this:

    void Add(this List<MyClass> values, int val1, char val2)

    { values.Add(new MyClass(val1, val2)); }

  • Link to the Add() as extension issue (please vote):

    visualstudio.uservoice.com/.../2743568-support-c-add-extension-methods-for-collection-

  • Actually you did know about this in September of 2007. kirillosenkov.blogspot.com/.../c-30-collection-initializers-duck.html :)

    The pattern matching (duck typing?) rules require the type has to implement IEnumerable and an Add method for collection initialization syntax to be supported.

    At the time you ended by wondering why IEnumerable was required. I think it's because the language designers didn't want to add collection initialization syntax support for purely mathematical methods. See Mads' post: blogs.msdn.com/.../what-is-a-collection_3f00_.aspx

  • Jacob: LOL, yes, but at that time I didn't realize the Add method can have arbitrary signature :)

Page 1 of 1 (7 items)
Leave a Comment
  • Please add 3 and 4 and type the answer here:
  • Post