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

dfty9e19  于 5个月前  发布在  C#
关注(0)|答案(1)|浏览(87)

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

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");
            }
        }
    }
}

字符串
这是一个令人惊讶的结果,

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


我认为问题在于LINQ表达式。内部LINQ表达式是正确的,因为Barstruct,它在重复struct时使用值语义,但外部LINQ表达式将重复引用同一个List<T>,因为List<T>是一个类,因此使用引用语义。
我的问题是(1)上面对这个问题的解释正确吗?(2)在不使用显式嵌套循环的情况下初始化List<List<T>>的更好方法是什么?

toe95027

toe950271#

外部列表的内部列表包含相同的val值的原因是Enumerable.Repeat<List<Bar>>(List<Bar>, Int32)的第一个参数只计算一次,所以它们实际上是相同的列表重复rows次。
为了让自己相信这一点,你可以使用object.ReferenceEquals()来Assertmat的所有项都是相等的:

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

字符串
因此,foreach(var i in Enumerable.Range(0, rows * cols))循环会多次覆盖该列表的内容,最终迭代的内容将获胜。
演示小提琴#1 here
假设你想要不同的列表,包含以下内容:

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


您可以使用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();


演示小提琴#2 here

相关问题