Лексическая область видимости в C # лямбда / анонимные делегаты - PullRequest
4 голосов
/ 18 февраля 2010

Я хочу проверить, переполняется ли простое математическое выражение (используя checked и catch(OverflowException)), но без необходимости каждый раз использовать блок try-catch Таким образом, выражение (не результат!) Должно быть передано функции checkOverflow, которая затем действует соответствующим образом в случае переполнения.

Это то, что я пытался, но не работает, так как, похоже, нет лексической области видимости для лямбда-выражений.

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 голосов
/ 18 февраля 2010

Числа с плавающей точкой в ​​.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  // 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

Это не то, как проверено, работает, проверено определяет, какие инструкции генерируются во время компиляции (некоторые инструкции перехватывают, некоторые нет)

Проверенный блок НЕ влияет на код, определенный вне его.Например, просто используя функции:

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.Таким образом, два обращения к нему не могут изменить его, чтобы проверить или нет по факту.Проверенный блок вокруг первого вызова в приведенном выше примере на самом деле не влияет на результирующий IL.Там нет операций определенных внутри него, которые имеют значение для него.

Таким образом, вашей первоначальной идеей было определить арифметическую операцию в одном месте (без проверки), а затем запустить ее в другой точке (точно так же, каквызов функции), но проверенная инструкция не может повлиять на код, который не был окружен во время компиляции.

лямбда-выражения преобразуются либо в деревья выражений, либо в анонимный делегат (возможно, при поддержке необходимых синтетических классов для хранения и поддержки любых переменных, связанных с замыканием).В обоих случаях проверенный аспект любой их части полностью определяется там, где они определены, а не там, где они вызываются.

...