これは、事実上、インラインリストの初期化がスタックに対して大きすぎるという事実が原因です。シナリオがほぼ同じである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#コンパイラは、代わりに、それぞれに必要な数の参照をスタックにプリロードすることもできます(プロパティの割り当てごとにそれぞれをポップします)が、ローカルの使用がはるかに優れたスタックを悪用します。
また、単一のローカルを使用することもできます。これは、それぞれが単純に書き込まれ、配列インデックスによってリストに格納されるため、ローカルが再び必要になることはありません。これは、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 };
}
今のところ、ILはGetInts()
単に.maxstack 8
頭にあり、地元の人はいません。イテレータ関数GetIntsHelper()
を見ると、次のようになっています。
.maxstack 2
.locals init (
[0] class SomeExample/'<GetIntsHelper>d__5'
)
だから今、私たちはそれらのメソッドでそれらすべてのローカルを使用するのをやめました...
しかし..。
コンパイラによって自動的に生成されたクラスSomeExample/'<GetIntsHelper>d__5'
を見てください-そして、ローカルがまだそこにあることがわかります-それらはちょうどそのクラスのフィールドにプロモートされました:
.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'
したがって、問題は、シナリオに適用した場合、そのオブジェクトの作成もスタックを爆破するのでしょうか?おそらくそうではありません。メモリ内では、大規模な配列を初期化しようとするようなものである必要があります。100万要素の配列は非常に受け入れられます(実際には十分なメモリがあると想定しています)。
つまり、各要素のメソッドを使用するように移行するだけで、コードを非常に簡単に修正できる可能性があります。IEnumerable
yield
ただし、ベストプラクティスでは、これを静的に定義する必要がある場合は、データをディスク上の埋め込みリソースまたはファイルに追加し(XMLおよびLinq to XMLが適切な選択である可能性があります)、オンデマンドでそこからロードすることを検討してください。
さらに良い-データベースに貼り付けてください:)