最近我写了一些类似于以下的代码
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>>并产生不同行的更好方法是什么?
外部列表的内部列表包含相同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在这里。