首页 > 解决方案 > 递归地将嵌套的 lambda 表达式构建为变量,在特定情况下奇怪的 stackoverflow。意外行为

问题描述

首先,一些示例代码。

我对我的问题进行了以下抽象,您可以将其放入 linqpad。

Func<int, int> recursiveSomethingIntegers(int b){
    if(b == 1){
        return x => x * x;
    }
    if(b == 2){
        return x => x * x + 1;
    }
    var intArray = new int[]{1, 2};
    Func<int, int> lExpression = null;
    Func<int, int> someOtherExpression = null;
    if(b == 3){
        foreach(var a in intArray){
            if(lExpression == null){
                Console.WriteLine("Called only once");
                lExpression = recursiveSomethingIntegers(a);
                continue;
            }
            else{
                Console.WriteLine("Also called only once");

                // This statement leads to the error
                lExpression = x => lExpression(a) + 2;
                // This one, which has the same initialization doesn't.
                someOtherExpression = x => lExpression(a) + 2;
                // Comment out either one of the above
            }
        }
    }
    return someOtherExpression ?? lExpression;
}

var someExpression = recursiveSomethingIntegers(3);

Console.WriteLine(someExpression(3));

如您所见,代码完全没有意义。尽管如此,我相信重用变量所产生的问题并不是有意的设计。

在 Visual Studio 中调试它时,它给了我一个“访问冲突”错误,而不是异常,至少在我的真实代码中是这样。

如果我正常运行它,它会给出一个 StackOverflowException 。

当 lambda 正在执行时,Console.WriteLine(someExpression(3)); 它会卡住lExpression = x => lExpression(a) + 2;并不断添加到堆栈中,显然会导致堆栈溢出。同时使用另一个变量很好。

如果您使用普通变量而不是 lambda 表达式来执行此操作,那很好。

在我的用例中,我需要再次使用相同的变量名,因为我的 foreach 循环应该能够运行任意数量的循环。

我的问题是这是否真的是无意的 C#/编译器/运行时行为?因为对我来说,这似乎违反了语言的设计。

编辑:我正在更多地研究调试器,我认为在初始化中lExpression = x => lExpression(a) + 2;改进了 BOTHlExpression作为第一个lExpression.

这就像x = x + 1将 BOTH 重新定义x为 的结果x + 1,这没有任何意义。

EDIT2:解决这个问题的方法:而不是

lExpression = x => lExpression(a) + 2;

做:

var clonedExpression = lExpression.Clone() as Func<int, int>;
lExpression = x => clonedExpression(a) + 2;

我不完全理解为什么这会起作用。

另一个示例,如果您希望使用 Visual Studio 项目进行尝试:

using System;

namespace ConsoleApp1
{
    class Program
    {

        public Program()
        {
            var someExpression = recursiveSomethingIntegers(3);
            System.Diagnostics.Debug.WriteLine(someExpression(3));
        }
        static void Main(string[] args)
        {
            var p = new Program();
        }

        Func<int, int> recursiveSomethingIntegers(int b)
        {
            if (b == 1)
            {
                return x => x * x;
            }
            if (b == 2)
            {
                return x => x * x + 1;
            }
            var intArray = new int[] { 1, 2 };
            Func<int, int> lExpression = null;
            Func<int, int> someOtherExpression = null;
            if (b == 3)
            {
                foreach (var a in intArray)
                {
                    if (lExpression == null)
                    {
                        System.Diagnostics.Debug.WriteLine("Called only once");
                        lExpression = recursiveSomethingIntegers(a);
                        continue;
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine("Also called only once");

                        // This statement leads to the error
                        lExpression = x => lExpression(a) + 2;
                        // This one, which has the same initialization, doesn't.
                        someOtherExpression = x => lExpression(a) + 2;
                        // Comment out either one of the above, don't leave both uncommented
                    }
                }
            }
            return someOtherExpression ?? lExpression;
        }
    }
}

标签: c#lambdastack-overflow

解决方案


推荐阅读