Как цикл for может быть связан с переполнением стека? - PullRequest
6 голосов
/ 03 июля 2019

Я встретил фрагмент кода, который вызовет исключение переполнения стека, только когда цикл for повторяется определенное количество раз.

Вот код:

public class Stackoverflow
{
    public static void Test()
    {
        List<int> list = new List<int>() {1, 2};

        Container container = new Container {List = list};
        for (int i = 0; i < 10000; i++)     // This matters
        {
            foreach (var item in container.List)
            {
                Console.WriteLine(item);
            }
        }
    }
}

public class Container
{
    private IEnumerable<int> list;

    public IEnumerable<int> List
    {
        get
        {
            //return list.OrderBy(x => x);  <- This is OK
            list = list.OrderBy(x => x);    // This is not
            return list;
        }
        set { list = value; }
    }

}

Когда выполняется метод Test (), вы можете увидеть длинные серии «1» и «2», напечатанные на экране до того, как ошибка действительно произойдет. (что, я думаю, означает цикл for продвигается правильно)

И исключение переполнения стека не возникает, если условие становится «i <1000» (или меньше). </p>

Дамп памяти показал, что «List.Orderby», вероятно, является прямой причиной проблемы

Я знаю, что писать "get" - это плохая практика, но я не понимаю, что вызывает здесь исключение переполнения стека и почему оно кажется накопительным, а не рекурсивной смертельной ловушкой.

Это, вероятно, ловушка в коде перечисления, или что-то от компилятора, или просто мой недосмотр? В любом случае, ищите объяснения, спасибо за вашу помощь.

Stacktrace здесь

В тексте:

000000e86215e460 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e4a0 00007ffe849ce396 (MethodDesc 00007ffe844a4ce8 +0x66 System.Linq.Buffer`1[[System.Int32, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<Int32>))
000000e86215e4c0 00007ffe86bad279 (MethodDesc 00007ffe866d7630 +0x19 System.StubHelpers.StubHelpers.SafeHandleRelease(System.Runtime.InteropServices.SafeHandle))
000000e86215e510 00007ffe28670fe7 (MethodDesc 00007ffe28567e80 +0x47 System.Linq.OrderedEnumerable`1+<GetEnumerator>d__1[[System.Int32, mscorlib]].MoveNext())
000000e86215e520 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e560 00007ffe849ce396 (MethodDesc 00007ffe844a4ce8 +0x66 System.Linq.Buffer`1[[System.Int32, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<Int32>))
000000e86215e580 00007ffe86c5b552 (MethodDesc 00007ffe867e3f60 +0xf2 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr))
000000e86215e5d0 00007ffe28670fe7 (MethodDesc 00007ffe28567e80 +0x47 System.Linq.OrderedEnumerable`1+<GetEnumerator>d__1[[System.Int32, mscorlib]].MoveNext())
000000e86215e5e0 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e5e8 00007ffe86c5b526 (MethodDesc 00007ffe867e3f60 +0xc6 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr))
000000e86215e620 00007ffe849ce396 (MethodDesc 00007ffe844a4ce8 +0x66 System.Linq.Buffer`1[[System.Int32, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<Int32>))
000000e86215e690 00007ffe28670fe7 (MethodDesc 00007ffe28567e80 +0x47 System.Linq.OrderedEnumerable`1+<GetEnumerator>d__1[[System.Int32, mscorlib]].MoveNext())
000000e86215e6a0 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e6e0 00007ffe849ce396 (MethodDesc 00007ffe844a4ce8 +0x66 System.Linq.Buffer`1[[System.Int32, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<Int32>))
000000e86215e710 00007ffe86b9b46a (MethodDesc 00007ffe86950f90 +0x8a System.IO.StreamWriter.Flush(Boolean, Boolean))
000000e86215e750 00007ffe28670fe7 (MethodDesc 00007ffe28567e80 +0x47 System.Linq.OrderedEnumerable`1+<GetEnumerator>d__1[[System.Int32, mscorlib]].MoveNext())
000000e86215e760 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e7a0 00007ffe849ce396 (MethodDesc 00007ffe844a4ce8 +0x66 System.Linq.Buffer`1[[System.Int32, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<Int32>))
000000e86215e7d0 00007ffe28670da5 (MethodDesc 00007ffe28565c68 +0xe5 ConsoleTest.Container.get_List())
000000e86215e810 00007ffe28670fe7 (MethodDesc 00007ffe28567e80 +0x47 System.Linq.OrderedEnumerable`1+<GetEnumerator>d__1[[System.Int32, mscorlib]].MoveNext())
000000e86215e820 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e860 00007ffe2867065f (MethodDesc 00007ffe28565b98 +0x11f ConsoleTest.Stackoverflow.Test())
000000e86215e900 00007ffe286704ba (MethodDesc 00007ffe28565ac0 +0x3a ConsoleTest.Program.Main(System.String[]))

1 Ответ

8 голосов
/ 03 июля 2019

Проблема в том, что ваше свойство get заменяет переменную list каждый раз новым объектом, определяемым list.OrderBy(x => x);.

Обратите внимание, что OrderBy(x => x) пока не выполняет никакого упорядочения, он создает объект, который определяет, как возвращать элементы, который выполняется, только если / когда вы выполняете его, например, с помощью. foreach.

Я постараюсь показать последствия этого:

  • Перед первым запуском list равно List<int>() {1, 2}
  • После одного запуска list равняется (List<int>() {1, 2}).OrderBy(x => x)
  • После того, как он побежал дважды, list равняется (List<int>() {1, 2}).OrderBy(x => x).OrderBy(x => x)
  • После того, как он запустился 10000 раз ... Я даже не буду пытаться показать, что содержит list.

Перечисление того, что вложенный в 10000 объектов объект вызывает 10000 объектов перечислителя, каждый из которых выполняет упорядоченный поиск предыдущего, пока не будет обнаружен исходный источник List<int>. Очевидно, до исчерпания списка источников не хватает места.

Исправление состоит в том, чтобы не переустанавливать список каждый раз, или (если вы чувствуете, что должны по какой-то причине), затем заставить его «делать» OrderBy с помощью ToList ():

get
{
    list = list.OrderBy(x => x).ToList(); // creates a new list and does not keep to OrderBy() object
    return list;
}

Это все равно приведет к избыточному переупорядочению и переназначению при каждом доступе к свойству. Если вам нужно упорядочивать всегда, гораздо эффективнее будет поместить эту логику в установщик, поэтому это произойдет только один раз:

get { return list; }
set { list = value.OrderBy(x => x).ToList(); }
...