How to simplify the initialization of List<List<Int32>> as IEnumerable<IEnumerable<Int32>>?

Viewed 152

I am doing the following collection initialisation:

Int32[,] v1 = new Int32[2, 2] { { 1, 2 }, { 3, 4 } };

IEnumerable<IEnumerable<Int32>> v2 = new List<List<Int32>> { { 2, 3 }, { 3, 4 } };

On the second line I get the error:

No overload for method 'Add' takes 2 arguments

Is there a way, using the latest C# version, to create an IEnumerable<IEnumerable<Int32>> without adding new List<Int32> for each item inside the main collection?

IEnumerable<IEnumerable<Int32>> v2 = new List<List<Int32>> { 
  new List<Int32> { 2, 3 }, 
  new List<Int32> { 3, 4 } 
};
2 Answers

There's no need to write a wrapper around List<List<T>>!

Collection initializers are just syntactic sugar to call a method called Add declared by the object, and List<T> doesn't declare an Add method which takes 2 parameters (or any number of parameters above 1).

However, a collection initializer will also call an extension method called Add. This means we can write this:

public static class ListExtensions
{
     public static void Add<T>(this List<List<T>> list, params T[] items)
     {
         list.Add(new List<T>(items));
     }
}

Which lets us then write:

IEnumerable<IEnumerable<int>> collection = new List<List<int>>()
{
    { 1, 2 },
    { 3, 4 },
};

See it working here.


It's worth noting that this sort of Add method can only be added as an extension method -- it would be impossible for List<T> to declare it itself. The reason for this is that it only makes sense when the list contains other lists, and there's no way to define an instance method which only applies for some types of list. However, you can add all sorts of restrictions like this on extension methods. You can write an extension method on List<T> which only applies when T is a class, or indeed only when T is another list!

From the Object and Collection Initializers article in the C# Programming Guide:

Collection initializers let you specify one or more element initializers when you initialize a collection type that implements IEnumerable and has Add with the appropriate signature as an instance method or an extension method.

So, the problem here is that no appropriate overload for List<T>.Add() takes a variable number of arguments.

If you define a custom collection type you can add an Add() method that takes a params argument:

class NestedList<T> : IEnumerable<IEnumerable<T>>
{
    List<List<T>> _list;

    public NestedList()
    {
        _list = new List<List<T>>();
    }

    public void Add(params T[] innerItems)
    {
        _list.Add(new List<T>(innerItems));
    }

    public IEnumerator<IEnumerable<TI>> GetEnumerator()
    {
        return _list.GetEnumerator();
    }

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

Now that we have an Add() method that takes a variable amount of arguments, you can use nested initializers:

IEnumerable<IEnumerable<int>> v2 = new NestedList<int> { { 1, 2 }, { 3, 4 } };
Related