Search code examples
c#linq

How do you populate a List of Lists in C# using LINQ?


Recently I wrote some code akin to the following

namespace Foo
{
    public struct Bar
    {
        public float val;

        public Bar(float val) {  this.val = val; }  
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            int rows = 3;
            int cols = 4;

            List<List<Bar>> mat = Enumerable.Repeat(
                   Enumerable.Repeat(default(Bar), cols).ToList(),
                   rows
               ).ToList();
            
            foreach(var i in Enumerable.Range(0, rows * cols))
            {
                int row = i / cols;
                int col = i % cols;
                mat[row][col] = new Bar((float)i);
            }

            foreach (var row in Enumerable.Range(0, rows))
            {
                foreach (var col in Enumerable.Range(0, cols))
                {
                    Console.Write(mat[row][col].val + " ");
                }
                Console.Write("\n");
            }
        }
    }
}

which yields the surprising (to me) output of

8 9 10 11
8 9 10 11
8 9 10 11

I believe the problem is that in the LINQ expression. The inner LINQ expression is correct because since Bar is a struct it will use value semantics when repeating the struct, but the outer LINQ expression will then repeat references to the same List<T> because a List<T> is a class and thus uses reference semantics.

My questions are (1) is the above explanation of this problem correct? and (2) what is a better way to initialize a List<List<T>> without using explicit nested loops and that results in distinct rows?


Solution

  • The reason the inner lists of the outer list contain identical val values is that the first argument to Enumerable.Repeat<List<Bar>>(List<Bar>, Int32) is evaluated only once, so they are in fact the same list repeated rows times.

    To convince yourself of that, you could assert that all of the items of mat are equal using object.ReferenceEquals():

    Assert.That(mat.All(row => object.ReferenceEquals(row, mat[0]))); // Does not throw.
    

    Thus your foreach(var i in Enumerable.Range(0, rows * cols)) loop overwrites the contents of that single list multiple times, with the contents of the final iterations winning.

    Demo fiddle #1 here.

    Assuming you want distinct lists with the following contents:

    0 1 2 3 
    4 5 6 7 
    8 9 10 11 
    

    You could use nested calls to Enumerable.Range(int start, int count) concisely to create your jagged list of distinct lists as follows:

    var mat = Enumerable.Range(0, rows)
        .Select(iRow => Enumerable.Range(iRow*cols, cols).Select(iCell => new Bar(iCell)).ToList())
        .ToList();
    

    Demo fiddle #2 here.