我有一个简单的类,NumberCollection
,它有一个类型为List<int>
的属性Numbers
。
public class NumberCollection
{
public IEnumerable<int> Numbers { get; set; }
}
我创建了一个NumberCollection
列表:
var list = new List<NumberCollection>
{
new NumberCollection
{
Numbers=new List<int>{1,2,3},
},
new NumberCollection
{
Numbers=new List<int>{4,5,6},
},
};
现在,我只想选择list
的第一个元素,并将所有NumberCollections
的串联Numbers
保存到第一个元素。我运行了这段代码:
var singleCollection = list.FirstOrDefault();
singleCollection.Numbers = list.SelectMany(c => c.Numbers);
它可以编译并运行良好,但尝试访问singleCollection.Numbers
的任何成员会使调试器崩溃。在即时窗口中评估singleCollection.Numbers.ElementAt(0)
会给出堆栈溢出异常。这是怎么回事?
您正在创建一个循环引用(因此是堆栈溢出(。
您将列表中的first item
分配给singleCollection
,然后尝试将第一项的编号重新分配给其自己的Numbers
属性。您需要通过ToList
添加新引用,或者仅通过Concat
添加值。
var singleCollection = list.FirstOrDefault();
singleCollection.Numbers = list.SelectMany(c => c.Numbers).ToList() ;
Console.WriteLine(singleCollection.Numbers.ElementAt(0));
var singleCollection2 = list.FirstOrDefault();
singleCollection2.Numbers.Concat(list.SelectMany(c => c.Numbers));
Console.WriteLine(singleCollection2.Numbers.ElementAt(0));
编辑,您还可以首先创建一个新的"引用"列表,方法是使用选择,然后创建 NumberCollection 的新实例。
var singleCollection3 = list.Select(x => new NumberCollection { Numbers = x.Numbers.ToList() }).FirstOrDefault();
singleCollection3.Numbers = list.SelectMany(c => c.Numbers);
Console.WriteLine(singleCollection3.Numbers.ElementAt(0));
重要提示:使用 SelectMany,您使用的是延迟执行。这意味着表达式的计算将延迟到实际使用其值。这就是为什么在您实际尝试访问一个数字之前,StackOverflow不会发生的原因。
你会从这段代码中获得相同的行为:
IEnumerable<int> a = new[] { 1 };
a = a.SelectMany(_ => a);
a.First();
由于SelectMany()
使用延迟执行,因此在调用a.First()
之前不会计算其 lambda 表达式。至此,在其 lambda 中引用的a
不再指向new[] { 1 }
。相反,它指向a.SelectMany(_ => a)
,而这又只能通过迭代a
来解决。因此,有一个循环引用,调用堆栈越来越深,直到 dotnet 运行时因堆栈溢出异常而放弃。