Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference Between Collection Initializer Syntaxes

Tags:

c#

c#-6.0

MSDN states that:

By using a collection initializer you do not have to specify multiple calls to the Add method of the class in your source code; the compiler adds the calls.

They also give this example, using the newer collection initialization syntax with brackets:

var numbers = new Dictionary<int, string> { 
    [7] = "seven", 
    [9] = "nine", 
    [13] = "thirteen" 
};

However, when checking the IL code generated, it seems that this code does not at all result in any calls to the Add method, but to rather to one set_item, like so:

IL_0007: ldstr        "seven"
IL_000c: callvirt     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<int32, string>::set_Item(!0/*int32*/, !1/*string*/)

The "old" syntax with curly brackets in contrast gives the following:

// C# code:
var numbers2 = new Dictionary<Int32, String>
{
    {7, "seven"},
    {9, "nine"},
    {13, "thirteen"}
};

// IL code snippet:
// ----------
// IL_0033: ldstr        "seven"
// IL_0038: callvirt     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<int32, string>::Add(!0/*int32*/, !1/*string*/)

... As you can see, a call to Add is the result, as expected. (One can only assume that the text on MSDN mentioned above is yet to be updated.)

I've so far discovered one case where this difference actually matters, and that is with the quirky System.Collections.Specialized.NameValueCollection. This one allows for one key to point to more than one value. Initialization can be done in both ways:

const String key = "sameKey";
const String value1 = "value1";
const String value2 = "value2";

var collection1 = new NameValueCollection
{
    {key, value1},
    {key, value2}
};

var collection2 = new NameValueCollection
{
    [key] = value1,
    [key] = value2
};

... But because of the differences in how only the former actually calls the NameValueCollection::Add(string, string), the results differ when looking at the contents of each collection;

collection1[key] = "value1,value2"

collection2[key] = "value2"

I realize that there's a connection between the old syntax and the IEnumerable interface, and how the compiler finds the Add method by naming convention etcetera. I allso realize the benefits of any indexer type being subject to the new syntax, as discussed in this SO answer before.

Perhaps these are all expected features, from your point of view, but the implications had not occurred to me, and I'm curious to know more.

So, I wonder if there's a source of documentation at MSDN or elsewhere that clarifies this difference in behavior that comes with the choice of syntax. I also wonder if you know of any other examples where this choice may have such an impact as when initializing a NameValueCollection.

like image 218
Oskar Lindberg Avatar asked Aug 23 '16 13:08

Oskar Lindberg


People also ask

What is collection initializer?

C# 3.0 (. NET 3.5) introduced Object Initializer Syntax, a new way to initialize an object of a class or collection. Object initializers allow you to assign values to the fields or properties at the time of creating an object without invoking a constructor.

What is object initializer syntax?

The object initializers syntax allows you to create an instance, and after that it assigns the newly created object, with its assigned properties, to the variable in the assignment.

How are Initializers executed in C#?

Initializers execute before the base class constructor for your type executes, and they are executed in the order in which the variables are declared in your class. Using initializers is the simplest way to avoid uninitialized variables in your types, but it's not perfect.

What is object initialization why it is required?

In object initializer, you can initialize the value to the fields or properties of a class at the time of creating an object without calling a constructor. In this syntax, you can create an object and then this syntax initializes the freshly created object with its properties, to the variable in the assignment.


1 Answers

I suppose for the ultimate clarification, you have to go to the specification. The C# 6 spec is not 'officially' released, but there is an unofficial draft available.

What's interesting here is that, despite its location in the Programming Guide, the indexer syntax is not a collection initializer, it's an object initializer. From 7.6.11.3 'Collection Initializers':

A collection initializer consists of a sequence of element initializers, enclosed by { and } tokens and separated by commas. Each element initializer specifies an element to be added to the collection object being initialized, and consists of a list of expressions enclosed by { and } tokens and separated by commas. ... The collection object to which a collection initializer is applied must be of a type that implements System.Collections.IEnumerable or a compile-time error occurs. For each specified element in order, the collection initializer invokes an Add method on the target object with the expression list of the element initializer as argument list

And from 7.6.11.2 'Object Intializers':

An object initializer consists of a sequence of member initializers, enclosed by { and } tokens and separated by commas. Each member_initializer designates a target for the initialization. An identifier must name an accessible field or property of the object being initialized, whereas an argument_list enclosed in square brackets must specify arguments for an accessible indexer on the object being initialized.

Take this as an example:

public class ItemWithIndexer
{
    private readonly Dictionary<string, string> _dictionary = 
        new Dictionary<string, string>();

    public string this[string index]
    {
        get { return _dictionary[index]; }
        set { _dictionary[index] = value; }
    }
}

Note that this class does not meet the requirements to have a collection initializer applied: it does not implement IEnumerable or have an Add method, so any attempt to initialize in this way would result in a compile-time error. This object initializer targeting the indexer will compile and work, however (see this fiddle):

var item = new ItemWithIndexer
{
    ["1"] = "value"
};
like image 193
Charles Mager Avatar answered Sep 23 '22 20:09

Charles Mager