Динамическое "Scoping" C# Проверенное выражение

Можно ли (в C#) вызвать checked(...) выражение иметь динамическую "область видимости" для проверки переполнения? Другими словами, в следующем примере:

int add(int a, int b)
{
    return a + b;
}
void test()
{
    int max = int.MaxValue;
    int with_call = checked(add(max, 1)); // does NOT cause OverflowException
    int without_call = checked(max + 1);  // DOES cause OverflowException
}

потому что в выражении checked(add(max, 1))вызов функции вызывает переполнение, нет OverflowException бросается, даже если во время динамического экстента checked(...) выражение.

Есть ли способ вызвать оба способа оценки int.MaxValue + 1 бросить OverflowException?

РЕДАКТИРОВАТЬ: Ну, либо скажите мне, если есть способ, или дайте мне лучший способ сделать это (пожалуйста).

Причина, по которой я думаю, что мне это нужно, в том, что у меня есть такой код:

void do_op(int a, int b, Action<int, int> forSmallInts, Action<long, long> forBigInts)
{
    try
    {
        checked(forSmallInts(a, b));
    }
    catch (OverflowException)
    {
        forBigInts((long)a, (long)b);
    }
}
...
do_op(n1, n2, 
    (int a, int b) => Console.WriteLine("int: " + (a + b)),
    (long a, long b) => Console.WriteLine("long: " + (a + b)));

Я хочу это напечатать int: ... если a + b находится в int диапазон и long: ... если переполнение малого целого числа переполняется. Есть ли способ сделать это лучше, чем просто менять каждый Action (которых у меня много)?

4 ответа

Решение

Короче говоря, нет, для проверенных блоков или выражений невозможно иметь динамическую область видимости. Если вы хотите применить это во всей своей кодовой базе, вам следует добавить его к опциям компилятора.

Проверенные выражения или проверенные блоки должны использоваться там, где эта операция действительно происходит.

    int add(int a, int b)
    {
        int returnValue = 0;

        try
        {
            returnValue = checked(a + b);
        }
        catch(System.OverflowException ex)
        {
            //TODO: Do something with exception or rethrow
        }

        return returnValue;
    }

    void test()
    {
        int max = int.MaxValue;
        int with_call = add(max, 1);
    }

Вы не должны перехватывать исключения как часть естественного потока вашей программы. Вместо этого вы должны предвидеть проблему. Есть довольно много способов сделать это, но при условии, что вы просто заботитесь о int а также long и когда сложение переполняется:

РЕДАКТИРОВАТЬ: используя типы, которые вы упоминаете ниже в своем комментарии вместо int а также long:

void Add(RFSmallInt a, RFSmallInt b)
{
    RFBigInt result = new RFBigInt(a) + new RFBigInt(b);
    Console.WriteLine(
        (result > RFSmallInt.MaxValue ? "RFBigInt: " : "RFSmallInt: ") + result);   
}

Это предполагает, что у вас есть конструктор для RFBigInt что способствует RFSmallInt, Это должно быть тривиально, как BigInteger имеет то же самое для long, Существует также явное приведение от BigInteger в long что вы можете использовать для "понижения" значения, если оно не переполняется.

Вы можете использовать Expression Tree и изменить его, чтобы ввести проверенный математический оператор и выполнить его. Этот образец не скомпилирован и не протестирован, вам придется немного подправить его.

   void  CheckedOp (int a, int b, Expression <Action <int, int>> small, Action <int, int> big){
         var smallFunc = InjectChecked (small);
         try{
               smallFunc(a, b);
         }catch (OverflowException oe){
               big(a,b);
         }
   }


   Action<int, int> InjectChecked( Expression<Action<int, int>> exp )
   {
          var v = new CheckedNodeVisitor() ;
          var r = v.Visit ( exp.Body);
          return ((Expression<Action<int, int>> exp) Expression.Lambda (r, r. Parameters) ). Compile() ;
   }


   class CheckedNodeVisitor : ExpressionVisitor {

           public CheckedNodeVisitor() {
           }

           protected override Expression VisitBinary( BinaryExpression be ) {
                  switch(be.NodeType){
                        case ExpressionType.Add:   
                                return Expression.AddChecked( be.Left, be.Right);
                  }
                  return be;
           }
   }

Исключение должно быть исключением, а не обычным программным потоком. Но давайте не будем об этом пока что:)

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

Это очень упрощенный подход для выполнения проверенного добавления вручную, если производительность не является проблемой. Довольно неплохо, если вы можете перегружать операторы типов, т.е. вы управляете типами.

public static int SafeAdd(int left, int right)
{
if (left == 0 || right == 0 || left < 0 && right > 0 || right < 0 && left > 0)
    // One is 0 or they are both on different sides of 0
    return left + right;
else if (right > 0 && left > 0 && int.MaxValue - right > left)
    // More than 0 and ok
    return left + right;
else if (right < 0 && left < 0 && int.MinValue - right < left)
    // Less than 0 and ok
    return left + right;
else
    throw new OverflowException();
}

Пример с вашими собственными типами:

public struct MyNumber 
{
  public MyNumber(int value) { n = value; }

  public int n; // the value

  public static MyNumber operator +(MyNumber left, MyNumber right)
  {
    if (left == 0 || right == 0 || left < 0 && right > 0 || right < 0 && left > 0)
      // One is 0 or they are both on different sides of 0
      return new MyNumber(left.n + right.n); // int addition
    else if (right > 0 && left > 0 && int.MaxValue - right > left)
      // More than 0 and ok
      return new MyNumber(left.n + right.n); // int addition
    else if (right < 0 && left < 0 && int.MinValue - right < left)
      // Less than 0 and ok
      return new MyNumber(left.n + right.n); // int addition
    else
      throw new OverflowException();
  }

  // I'm lazy, you should define your own comparisons really
  public static implicit operator int(MyNumber number) { return number.n; }
}

Как я уже говорил ранее, вы потеряете производительность, но получите исключения.

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