C# lambda/匿名委托中的词法作用域

4

我想检查一个简单的数学表达式是否会溢出(使用checkedcatch(OverflowException)),但不需要每次都使用try-catch块。因此,表达式(而不是结果!)应该传递给一个函数checkOverflow,在溢出的情况下采取相应的措施。

这是我尝试过的,但由于lambda表达式似乎没有词法作用域,所以它不起作用。

static void Main(string[] args)
{
    double g, h;

    // Check if the expression g+h would overflow, *without* the need to use
    // try/catch around the expression

    // Both of the following attempts fail because there's no lexical scoping
    checkOverflow((FloatReturningExpression) delegate() {return g+h;});
    checkOverflow(() => g+h);

    Console.ReadKey();
}

private static void checkOverflow(FloatReturningExpression exp)
{
    try
    {
        checked { double f = exp(); }
    }
    catch(OverflowException)
    {
        Console.WriteLine("overflow!");
    }
}

private delegate double FloatReturningExpression();

有什么解决方案吗?(使用 .NET 2,但不一定需要。)
1个回答

7
在 .Net 中,浮点数不会像整数运算那样溢出。当遇到特定情况时,它们会变成 Double.PositiveIfinity、Double.NegativeIfinity 或 Double.Nan(在数学运算无效的情况下)。请注意,由于浮点数在面对两个精度非常不同的数字时的行为,这也略微复杂。
Console.WriteLine(double.MaxValue);
Console.WriteLine(double.MaxValue * 2);
Console.WriteLine(double.MaxValue + 1);
Console.WriteLine(double.MaxValue + double.MaxValue);

提供:

1.79769313486232E+308
Infinity
1.79769313486232E+308
Infinity

此外,不清楚您的checkOverflow函数的目的是什么,只要写下它发生了什么?如果这就是您想要的,那么这种方法会起作用(我已经为您转换为int)。
void Main()
{
    int a, b;
    a = int.MaxValue;
    b = 1;

    // Check if the expression a+b would overflow, *without* the need to use
    // try/catch around the expression
    checkOverflow(() => {checked { return a+b; }});    
}       

private static void checkOverflow(Func<int> exp)
{
    try
    {
        exp();
    }
    catch(OverflowException)
    {
        Console.WriteLine("overflow!");
    }
}

我应该补充一下这个代码为什么能够运行的原因:
checked并不是影响变量的词法作用域,它是编译器解释的一个区域,表示所有在这里进行整数算术运算的代码都应该生成溢出陷阱指令。变量来自哪里并不重要,只有定义在哪里的代码才是重要的。
我认为你的思维模式可能是这样的:
checked  // enter a 'checked' state where all operations 
{        // (say on the current thread) are checked

code, function calls, etc. etc

}  // leave the checked mode, all operations are now unchecked

这不是 checked 的工作方式,checked 定义了在编译时发出哪些指令(一些指令会触发溢出,而另一些则不会)。

checked 块不会影响其外部定义的代码。例如,只使用函数:

int Times2(int a)
{
    return a * 2;
}

void TheresNoDifferenceHere()
{
    checked { Times2(int.MaxValue); }
    Times2(int.MaxValue);
}

Times2函数调用会被解析为类似以下的内容

IL_0000:  nop         
IL_0001:  ldarg.1     
IL_0002:  ldc.i4.2    
IL_0003:  mul         
IL_0004:  stloc.0     
IL_0005:  br.s        IL_0007
IL_0007:  ldloc.0     
IL_0008:  ret   

如果您曾经使用过

int Times2(int a)
{
    checked { return a * 2; }
}

IL_0000:  nop         
IL_0001:  nop         
IL_0002:  ldarg.1     
IL_0003:  ldc.i4.2    
IL_0004:  mul.ovf     
IL_0005:  stloc.0     
IL_0006:  br.s        IL_0008
IL_0008:  ldloc.0     
IL_0009:  ret         

请注意使用mul和mul.ovf之间的区别。因此,即使在事后,这两个调用也不能改变其是否被检查。以上示例中第一个调用周围的checked块实际上对生成的IL没有任何影响。其中没有任何一项操作在其内部定义且对其有影响。
因此,您最初的想法是在一个地方(不进行检查)定义算术运算,然后在另一个地方运行它(就像函数调用一样),但是'checked'指令无法影响编译时未包围的代码。 lambda表达式会转换为表达式树或匿名委托(可能由必需的合成类来支持和维护任何与闭包相关的变量)。在两种情况下,它们的任何部分的checked方面完全由它们定义的位置而不是它们被调用的位置定义。

你能解释一下为什么在try块中使用checked { exp(); }时,溢出没有被捕获,而在lambda中却可以吗?a+b是在什么时候被计算的? - AndiDog
@AndiDog,我编辑的部分没有解释清楚吗?我会尝试添加更多内容。 - ShuggyCoUk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接