选择对象属性上的多个,然后将结果保存回属性会导致堆栈溢出



我有一个简单的类,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 运行时因堆栈溢出异常而放弃。

最新更新