如何用LINQ正确初始化C#中的List<List<T>>?问题排查与优化
LINQ创建嵌套List<List>的问题解答
问题背景
近期编写C#代码时,尝试用LINQ创建List<List<Bar>>类型的嵌套列表(Bar为自定义struct),运行后所有行内容完全相同,不符合预期。推测原因是外层Enumerable.Repeat重复的是同一个List<T>的引用(List<T>是引用类型),而内层Repeat因Bar是值类型可生成独立实例。现咨询两个问题:
- 上述问题解释是否正确?
- 如何不使用显式嵌套循环,初始化具有独立行的
List<List<T>>?
原代码示例:
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
问题解答
1. 问题解释是否正确?
完全正确。
Enumerable.Repeat的行为是:对于值类型,会重复生成独立的实例;对于引用类型,只会重复返回同一个对象的引用。- 外层
Repeat传入的是Enumerable.Repeat(default(Bar), cols).ToList()的结果——一个List<Bar>实例(引用类型),所以rows次重复返回的都是这个列表的引用,所有行指向的是同一个底层列表。 - 内层
Repeat处理的是值类型Bar,每次都会生成新的Bar实例,所以内层列表的元素是独立的,但由于所有行共享同一个内层列表实例,最后一次赋值(i=8到11)会覆盖之前的内容,导致所有行显示相同结果。
2. 不使用显式嵌套循环初始化独立行的方法
用Enumerable.Range配合Select替代外层的Enumerable.Repeat,让每一行都通过lambda表达式创建新的List<Bar>实例:
List<List<Bar>> mat = Enumerable.Range(0, rows) .Select(_ => Enumerable.Repeat(default(Bar), cols).ToList()) .ToList();
修改后的代码运行后,输出结果符合预期:
0 1 2 3 4 5 6 7 8 9 10 11
原理是:Enumerable.Range(0, rows)生成0到rows-1的序列,Select中的每个lambda表达式都会执行一次Enumerable.Repeat(...)并调用ToList(),每次都会创建全新的List<Bar>实例,因此所有行都是独立的。
内容的提问来源于stack exchange,提问作者jwezorek
相关产品推荐
相关产品推荐

