这是因为实际上,您的内联列表初始化对于堆栈来说太大了 - 请参阅msdn 论坛上的这个非常 相关的问题,其中场景几乎相同。
.Net 中的方法同时具有堆栈深度和大小。AStackOverflowException
不仅是由堆栈上的调用次数引起的,而且是每个方法在堆栈中分配内存的总体大小。在这种情况下,您的方法太大了 - 这是由局部变量的数量引起的。
例如,考虑以下代码:
public class Foo
{
public int Bar { get; set;}
}
public Foo[] GetInts()
{
return new Foo[] { new Foo() { Bar = 1 }, new Foo() { Bar = 2 },
new Foo() { Bar = 3 }, new Foo() { Bar = 4 }, new Foo() { Bar = 5 } };
}
现在查看编译时该方法的引入 IL(这也是发布版本):
.maxstack 4
.locals init (
[0] class SomeExample/Foo '<>g__initLocal0',
[1] class SomeExample/Foo '<>g__initLocal1',
[2] class SomeExample/Foo '<>g__initLocal2',
[3] class SomeExample/Foo '<>g__initLocal3',
[4] class SomeExample/Foo '<>g__initLocal4',
[5] class SomeExample/Foo[] CS$0$0000
)
注意 - 之前的实际位/
,即SomeExample
取决于定义方法和嵌套类的命名空间和类 - 我必须编辑几次才能从我正在编写的一些正在进行的代码中删除类型名工作!
为什么都是当地人?由于执行内联初始化的方式。每个对象都是新的并存储在“隐藏”本地,这是必需的,以便可以在每个对象的内联初始化上执行属性分配Foo
(需要对象实例来生成属性集Bar
)。 这也说明了内联初始化只是一些 C# 语法糖。
在您的情况下,正是这些局部变量导致堆栈崩溃(其中至少有几千个仅用于顶级对象 - 但您也有嵌套的初始化程序)。
C# 编译器也可以将每个所需的引用数量预加载到堆栈中(为每个属性分配弹出每个引用),但这会滥用堆栈,使用局部变量会执行得更好。
它也可以使用单个 local,因为每个都只是写入,然后通过数组索引存储在列表中,不再需要 local 。这可能是 C# 团队需要考虑的一个问题 - 如果 Eric Lippert 偶然发现这个线程,他可能对此有一些想法。
现在,这项检查还为我们提供了一条围绕这种使用本地人的潜在途径来实现非常庞大的方法:使用迭代器:
public Foo[] GetInts()
{
return GetIntsHelper().ToArray();
}
private IEnumerable<Foo> GetIntsHelper()
{
yield return new Foo() { Bar = 1 };
yield return new Foo() { Bar = 2 };
yield return new Foo() { Bar = 3 };
yield return new Foo() { Bar = 4 };
yield return new Foo() { Bar = 5 };
}
现在,ILGetInts()
现在只是.maxstack 8
处于领先地位,没有本地人。查看GetIntsHelper()
我们拥有的迭代器函数:
.maxstack 2
.locals init (
[0] class SomeExample/'<GetIntsHelper>d__5'
)
所以现在我们已经停止在这些方法中使用所有那些本地人......
但是...
查看SomeExample/'<GetIntsHelper>d__5'
编译器自动生成的 class ——我们看到 locals 仍然存在——它们刚刚被提升为该类的字段:
.field public class SomeExample/Foo '<>g__initLocal0'
.field public class SomeExample/Foo '<>g__initLocal1'
.field public class SomeExample/Foo '<>g__initLocal2'
.field public class SomeExample/Foo '<>g__initLocal3'
.field public class SomeExample/Foo '<>g__initLocal4'
所以问题是 - 如果应用于您的场景,该对象的创建也会破坏堆栈吗?可能不会,因为在内存中它应该像尝试初始化大型数组 - 其中百万元素数组是完全可以接受的(假设在实践中有足够的内存)。
所以 - 你可以很简单地通过使用每个元素的IEnumerable
方法来修复你的代码。yield
但最佳实践表明,如果您绝对必须对此进行静态定义 - 考虑将数据添加到磁盘上的嵌入式资源或文件(XML 和 Linq to XML 可能是一个不错的选择),然后按需从那里加载。
更好的是 - 将其保存在数据库中:)