Jest to spowodowane faktem, że skutecznie, Twoja lista inline inicjalizacji jest zbyt duże w stosunku do stosu - zobacz bardzorelated question over on the msdn forums gdzie scenariusz jest prawie na identyczne.
Metoda z .NET ma zarówno głębokość stosu, jak i rozmiar. A StackOverflowException
jest spowodowane nie tylko liczbą wywołań na stosie, ale także ogólnym rozmiarem przydziału pamięci dla każdej metody w stosie. W tym przypadku twoja metoda jest po prostu zbyt duża - i jest to spowodowane liczbą zmiennych lokalnych.
Tytułem przykładu, rozważmy następujący kod:
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 } };
}
Spójrzmy teraz na lead-in IL tej metody, gdy skompilowany (jest to build release, zbyt):
.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
)
Uwaga - rzeczywisty bit przed numerem /
, tj. SomeExample
będzie zależał od przestrzeni nazw i klasy, w której zdefiniowana jest metoda i klasa zagnieżdżona - musiałem edytować kilka razy, aby usunąć nazwy typów z niektórych kodów postępu, które 'piszę w pracy!
Dlaczego wszyscy mieszkańcy? Ze względu na sposób inicjalizacji inline.Każdy obiekt jest nowszy i przechowywany w "ukrytym" lokalnym, jest to wymagane, aby można było przypisać właściwości w inicja- tywach początkowych każdego z komputerów Foo
(instancja obiektu jest wymagana do wygenerowania zestawu właściwości dla Bar
). Pokazuje to również, że inicjalizacja inline to tylko cukier syntaktyczny C#.
W twoim przypadku to ci miejscowi powodują obrażenia stosu (jest ich co najmniej kilka tysięcy dla obiektów najwyższego poziomu - ale masz również zagnieżdżone inicjatory).
C# kompilator mógł alternatywnie wstępnie załadować liczbę referencji wymaganych dla każdego na do stosu (popping każde jednorazowe dla każdego zadania własności), ale potem, że nadużywa stos, gdzie korzystanie z mieszkańcami będzie wykonywał dużo lepszy.
To może również korzystać z jednego lokalnego, ponieważ każdy jest tylko napisane zbyt, a następnie przechowywane w wykazie indeksu tablicy, lokalny nie jest jeszcze konieczna. To może być dla zespołu C# do rozważenia - Eric Lippert może mieć kilka przemyśleń na ten temat, jeśli natknie się na ten wątek.
Teraz badanie to daje nam również potencjalne trasy wokół tego wykorzystaniu miejscowych dla bardzo masywnej metody: Użyj iterator:
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 };
}
Teraz IL dla GetInts()
teraz po prostu ma .maxstack 8
na głowa, a nie miejscowi. Patrząc na iterator GetIntsHelper()
mamy:
.maxstack 2
.locals init (
[0] class SomeExample/'<GetIntsHelper>d__5'
)
Więc teraz my przestaliśmy używać tych wszystkich mieszkańców w tych metod ...
Ale ...
Spójrz na klasy SomeExample/'<GetIntsHelper>d__5'
, który został automatycznie wygenerowany przez kompilator - i widzimy, że miejscowi wciąż tam są - właśnie zostali awansowani na pola w tej klasie:
.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'
Pytanie brzmi: czy utworzenie tego obiektu spowoduje również strącenie stosu, jeśli zostanie zastosowane do scenariusza? Prawdopodobnie nie, ponieważ w pamięci powinno to być jak inicjowanie dużej tablicy - gdzie tablice z milionami elementów są całkiem dopuszczalne (zakładając w praktyce wystarczającą pamięć).
Dzięki temu można być w stanie naprawić swój kod, po prostu przechodząc do korzystania z metody IEnumerable
, że każdy element jest yield
.
Ale najlepsza praktyka mówi, że jeśli absolutnie musisz mieć to statycznie zdefiniowane - rozważ dodanie danych do osadzonego zasobu lub pliku na dysku (XML i Linq do XML może być dobrym wyborem), a następnie załadowanie go z tego miejsca żądanie.
Lepiej - przyklej w bazie danych :)
W jaki sposób uzyskujesz dostęp do listy? – Oded
JEEEEZ, naprawdę powinieneś rozważyć przechowywanie ich gdzie indziej, twarde kodowanie ponad 3500 przedmiotów! : | – mattytommo
@Oded - coś jak sprzedawcy var = DataHelper.Sellers; – Gaz