Custom Collection Initializers

Custom Collection Initializers

No, the compiler requires a method named Add for the collection initializer to work. This is defined in C# specification and cannot be changed:

C# Language Specification - 7.5.10.3 Collection Initializers


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, applying normal overload resolution for each invocation. Thus, the collection object must contain an applicable Add method for each element initializer. [emphasis mine]

Of course, the Add method can take more than one argument (like Dictionary<TKey, TValue>):

dic = new Dictionary<int, int> { 
{ 1, 2 },
{ 3, 4 }
};
// translated to:
dic = new Dictionary<int, int>();
dic.Add(1, 2);
dic.Add(3, 4);

Using collection initializer syntax on custom types?

I think you need to make a custom collection instead of List. Call it LookupItemTable, for example. Give that collection an Add(int, int, float, float) method and have it implement IEnumerable. For example:

class LookupItem
{
public int a;
public int b;
public float c;
public float d;
}

class LookupItemTable : List<LookupItem>
{
public void Add(int a, int b, float c, float d)
{
LookupItem item = new LookupItem();
item.a = a;
item.b = b;
item.c = c;
item.d = d;
Add(item);
}
}

private static LookupItemTable _lookupTable = new LookupItemTable {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 }
};

I've now tried the above code and it seems to work for me.

Create Dictionary-style collection initializer on custom class

To get the custom initialization to work like Dictionary you need to support two things. Your type needs to implement IEnumerable and have an appropriate Add method. You are initializing an Array, which doesn't have an Add method. For example

class PairList<T1, T2> : IEnumerable
{
private List<Pair<T1, T2>> _list = new List<Pair<T1, T2>>();

public void Add(T1 arg1, T2 arg2)
{
_list.Add(new Pair<T1, T2>(arg1, arg2));
}

IEnumerator IEnumerable.GetEnumerator()
{
return _list.GetEnumerator();
}
}

and then you can do

var temp = new PairList<int, string>
{
{0, "bob"},
{1, "phil"},
{0, "nick"}
};

C# Collection initialization syntax for use with custom matrix class?

Try this code. Your Add method must be public. Also, to make the code safe, you must add verifiers to check if the sizes of the m matrix and the provided data match.

private int currentRow = 0;
public void Add(params double[] a)
{
for (int c = 0; c < a.Length; c++)
{
m[currentRow, c] = a[c];
}
currentRow++;
}

If you don't provide all the rows, the remaining rows will have the elements with their defaults values. Also, note that this method can be called in the future, and it would throw an exception when the m matrix has all the rows filled.

Why does a combination of object and collection initializers use Add method?

First, it's not only for combination of object and collection initializers. What you are referring here is called nested collection initializers, and the same rule (or issue by your opinion) applies to nested object initializers. So if you have the following classes:

public class Foo
{
public Bar Bar { get; set; }
}

public class Bar
{
public string Baz { get; set; }
}

and you use the following code

var foo = new Foo
{
Bar = { Baz = "one" }
};

you'll get the same NRE at runtime because no new Bar will be created, but attempt to set Baz property of the Foo.Bar.

In general the syntax for object/collection initializer is

target = source

where the source could be an expression, object initializer or collection initializer. Note that new List<Bar> { … } is not a collection initializer - it's an object creation expression (after all, everything is an object, including collection) combined with collection initializer. And here is the difference - the idea is not to omit the new, but give you a choice to either use creation expression + object/collection initializer or only initializers.

Unfortunately the C# documentation does not explain that concept, but C# specification does that in the Object Initializers section:

A member initializer that specifies an object initializer after the equals sign is a nested object initializer, i.e. an initialization of an embedded object. Instead of assigning a new value to the field or property, the assignments in the nested object initializer are treated as assignments to members of the field or property. Nested object initializers cannot be applied to properties with a value type, or to read-only fields with a value type.

and

A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the target field, property or indexer, the elements given in the initializer are added to the collection referenced by the target.


So why is that? First, because it clearly does exactly what you are telling it to do. If you need new, then use new, otherwise it works as assignment (or add for collections).

Other reasons are - the target property could not be settable (already mentioned in other answers). But also it could be non creatable type (e.g. interface, abstract class), and even when it is a concrete class, except it is a struct, how it will decide that it should use new List<Bar> (or new Bar in my example) instead of new MyBarList, if we have

class MyBarList : List<Bar> { }

or new MyBar if we have

class MyBar : Bar { }

As you can see, the compiler cannot make such assumptions, so IMO the language feature is designed to work in the quite clear and logical way. The only confusing part probably is the usage of the = operator for something else, but I guess that was a tradeoff decision - use the same operator = and add new after that if needed.

Possible to mix object initializer and collection initializer?

Unfortunately it is not possible to mix object and collection initializers. The C# 3.0 specification defines an object creation expression in section 7.5.10.1 as:


object-creation-expression:
new type ( argument-listopt ) object-or-collection-initializeropt
new type object-or-collection-initializer

As you might expect, object-or-collection-initializer is either an object initializer or a collection initializer. There is no syntax available for combining then together.

Initialize get-only collection in object initializer from existing collection

No. You can't assign this read-only property from a collection initializer. It is read-only after all.

TheList = { "A", "B" } works since it calls Add on TheList (once for each item added), it doesn't create and assign a new instance, which it is not allowed to.

TheList = { existingList } doesn't work since there is a typing issue (TheList = { existingList[0] } does work).

The best option you have it to create a constructor parameter and drop your idea of using collection initializers for something it isn't fit for.

Using collection initializer on my own class

It should be "and", not "or".

Collection initializers are described in C# language specification, section 7.6.10.3 Collection initializers:

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, applying normal overload resolution for each invocation. Thus, the collection object must contain an applicable Add method for each element initializer.

It clearly states that the collection must implement IEnumerable and there needs to be an Add method. The call to the Add method is resolved using normal overload resolution process, so it can be an extension method, generic method etc.



Related Topics



Leave a reply



Submit