Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make inline array initialization work like e.g. Dictionary initialization?

Why is it possible to initialize a Dictionary<T1,T2> like this:

var dict = new Dictionary<string,int>() { 
    { "key1", 1 },
    { "key2", 2 }
};

...but not to initialize, say, an array of KeyValuePair<T1,T2> objects in exactly the same way:

var kvps = new KeyValuePair<string,int>[] {
    { "key1", 1 },
    { "key2", 2 }
};
// compiler error: "Array initializers can only be used in a variable 
// or field initializer.  Try using a new expression instead."

I realize that I could make the second example work by just writing new KeyValuePair<string,int>() { "key1", 1 }, etc for each item. But I'm wondering if it's possible to use the same type of concise syntax that is possible in the first example.

If it is not possible, then what makes the Dictionary type so special?

like image 635
McGarnagle Avatar asked Jun 24 '12 01:06

McGarnagle


3 Answers

The collection initializer syntax is translated into calls to Add with the appropriate number of parameters:

var dict = new Dictionary<string,int>();
dict.Add("key1", 1);
dict.Add("key2", 2);

This special initializer syntax will also work on other classes that have an Add method and implements IEnumerable. Let's create a completely crazy class just to prove that there's nothing special about Dictionary and that this syntax can work for any suitable class:

// Don't do this in production code!
class CrazyAdd : IEnumerable
{
    public void Add(int x, int y, int z)
    {
        Console.WriteLine(x + y + z); // Well it *does* add...
    }

    public IEnumerator GetEnumerator() { throw new NotImplementedException(); }
}

Now you can write this:

var crazyAdd = new CrazyAdd
{
    {1, 2, 3},
    {4, 5, 6}
};

Outputs:

6
15

See it working online: ideone

As for the other types you asked about:

  • It doesn't work on an array because it has no Add method.
  • List<T> has an Add method but it has only one parameter.
like image 121
Mark Byers Avatar answered Nov 17 '22 12:11

Mark Byers


It does work with the Dictionary, because it has an overload for Add that takes two arguments. Arrays dont even have an Add method, let alone one with two arguments.

The Dictionary class is specially designed to work with KeyValuePair<,> internally, that is the only reason you do not need the call the constructor manually, instead the two-argument Add is called and constructs the KeyValuePair under the hood.

Every other IEnumerable<KeyValuePair<,>> does not have this special implementation and therefore has to be initialized this way:

var array = new KeyValuePair<int, int>[] {
    new KeyValuePair<int, int>(1, 2),
    new KeyValuePair<int, int>(3, 4)
};

You can create the same behaviour with your own classes, like lets say you implement this:

class ThreeTupleList<T1, T2, T3> : List<Tuple<T1, T2, T3>>
{
    public void Add(T1 a, T2 b, T3 c)
    {
        this.Add(new Tuple<T1, T2, T3>(a, b, c));
    }

    // You can even implement a two-argument Add and mix initializers
    public void Add(T1 a, T2 b)
    {
        this.Add(new Tuple<T1, T2, T3>(a, b, default(T3)));
    }
}

you can initialize it like this, and even mix three-, two- and one-argument initializers:

var mylist = new ThreeTupleList<int, string, double>()
{
    { 1, "foo", 2.3 },
    { 4, "bar", 5.6 },
    { 7, "no double here" },
    null
};
like image 11
Philip Daubmeier Avatar answered Nov 17 '22 14:11

Philip Daubmeier


Your problem stems from the fact that it is an array, not a collection.

var kvps = new KeyValuePair<string,int>[] {
    { "key1", 1 },
    { "key2", 2 }
};

should really be:

var kvps = new KeyValuePair<string, int>[] {
    new KeyValuePair<string, int>("key1", 1),
    new KeyValuePair<string, int>("key2", 2)
};

The giveaway is the brackets. [] is an array. {} is a collection.

like image 3
Cole Tobin Avatar answered Nov 17 '22 14:11

Cole Tobin