Custom Collection Initializers
Asked Answered
P

2

56

Classes that implement IEnumerable and provide a public void Add(/* args */) function can be initialized like in the following example:

List<int> numbers = new List<int>{ 1, 2, 3 };

which calls the Add(int) function 3x after initializing the List<int>.

Is there a way to define this behavior explicitly for my own classes? For example, could I have the initializer call a function other than the appropriate Add() overload?

Predicate answered 22/3, 2010 at 21:12 Comment(0)
H
80

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);
Heterodoxy answered 22/3, 2010 at 21:13 Comment(4)
I could have sworn that it had to be a public method too - but it looks like I'm wrong. Now I'll have to fix the bit of C# in Depth I was reviewing today :(Shaer
@JonSkeet, it looks like compilation fails if the Add method isn't visible at the location where the initializer syntax is used.Krisha
@kpozin: Exactly - hopefully I did get round to fixing this before I shipped C# in Depth :)Shaer
VB.NET gets to use extension methods for Add() on initializers. :/Crifasi
L
4

Adding just as a sample answer of what works. AFAIK, only Add will work. Code snippet taken from Marius Schulz

// simple struct which represents a point in three-dimensional space
public struct Point3D
{
    public readonly double X;
    public readonly double Y;
    public readonly double Z;

    public Point3D(double x, double y, double z)
    {
        X = x;
        Y = y;
        Z = z;
    }
}

// implementation of a collection of points, which respects
// the compiler convention for collection initializers and
// therefore both implements IEnumerable<T> and provides
// a public Add method
public class Points : IEnumerable<Point3D>
{
    private readonly List<Point3D> _points;

    public Points()
    {
        _points = new List<Point3D>();
    }

    public void Add(double x, double y, double z)
    {
        _points.Add(new Point3D(x, y, z));
    }

    public IEnumerator<Point3D> GetEnumerator()
    {
        return _points.GetEnumerator();
    }

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

// instantiate the Points class and fill it with values like this:
var cube = new Points
{
    { -1, -1, -1 },
    { -1, -1,  1 },
    { -1,  1, -1 },
    { -1,  1,  1 },
    {  1, -1, -1 },
    {  1, -1,  1 },
    {  1,  1, -1 },
    {  1,  1,  1 }
};
Lyautey answered 25/11, 2015 at 23:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.