for循环如何与堆栈溢出相关?

ck1*_*521 6 c# stack-overflow

我遇到了一段代码,仅当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; }
    }

}
Run Code Online (Sandbox Code Playgroud)

当执行方法Test()时,您可以看到在屏幕上打印出一连串的“ 1”和“ 2”,然后才实际发生错误。(我认为这意味着for循环正在顺利进行)

如果条件变为“ i <1000”(或更小),则不会发生堆栈溢出异常。

内存转储显示“ List.Orderby”可能是问题的直接原因

我知道写这样的“ get”是一个坏习惯,但是我不明白是什么导致堆栈溢出异常,以及为什么它似乎是累积的而不是递归的死亡陷阱。

这可能是枚举代码的陷阱,还是编译器的陷阱,还是我的疏忽?无论如何,正在寻找解释,谢谢您的帮助。

这里的堆栈跟踪

在文字中:

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[]))
Run Code Online (Sandbox Code Playgroud)

Pet*_*r B 8

问题在于,您的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个Enumerator对象被旋转,每个对象都对前一个对象进行有序检索,直到List<int>遇到原始源为止。显然,它在到达源列表之前已用完空间。

解决方法是不要每次都重新分配列表,或者(如果您出于某种原因必须这样做)然后使用ToList()使它“执行” OrderBy:

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

每次访问该属性时,仍然会导致冗余的重新排序和重新分配。如果您始终需要订购,那么将逻辑放入设置器中将更加有效,因此只需执行一次:

get { return list; }
set { list = value.OrderBy(x => x).ToList(); }
Run Code Online (Sandbox Code Playgroud)

  • 可以说,更好的解决方案是更改set来合并OrderBy并将get保留为简单的值返回。这不需要在必要之前实例化列表,而无需嵌套灾难(以及更改吸气剂中的值,这只是糟糕的时期)。当然,直接返回list.OrderBy(...)的原始方法也很好。 (2认同)