Лексическая область видимости в C# лямбда / анонимные делегаты

Я хочу проверить, не переполнится ли простое математическое выражение (используя 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 ответ

Решение

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

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

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

Другие вопросы по тегам