如何使用 LINQ 在 C# 中填充列表的列表?

jwe*_*rek 1 c# linq

最近我写了一些类似于以下的代码

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");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这产生了令人惊讶的(对我来说)输出

8 9 10 11
8 9 10 11
8 9 10 11
Run Code Online (Sandbox Code Playgroud)

我认为问题出在 LINQ 表达式中。内部 LINQ 表达式是正确的,因为因为Bar是 a ,所以struct在重复 时它将使用值语义struct,但外部 LINQ 表达式将重复对其的引用List<T>,因为 aList<T>是一个类,因此使用引用语义。

我的问题是(1)以上对这个问题的解释是否正确?(2) 在不使用显式嵌套循环的情况下初始化 aList<List<T>>并产生不同行的更好方法是什么?

dbc*_*dbc 6

外部列表的内部列表包含相同val值的原因是, 的第一个参数仅Enumerable.Repeat<List<Bar>>(List<Bar>, Int32)计算一次,因此它们实际上是重复多次的相同列表rows

为了让自己相信这一点,您可以使用以下方法断言 的所有项目mat都是相等的object.ReferenceEquals()

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

因此,您的foreach(var i in Enumerable.Range(0, rows * cols))循环会多次覆盖该单个列表的内容,最终迭代的内容获胜。

演示小提琴#1在这里

假设您想要包含以下内容的不同列表:

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

您可以使用嵌套调用来Enumerable.Range(int start, int count)简洁地创建不同列表的锯齿状列表,如下所示:

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

演示小提琴#2在这里