Dynamic "Scoping" of C# Checked Expression

405 views Asked by At

Is it possible (in C#) to cause a checked(...) expression to have dynamic "scope" for the overflow checking? In other words, in the following example:

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
}

because in the expression checked(add(max, 1)), a function call causes the overflow, no OverflowException is thrown, even though there is an overflow during the dynamic extent of the checked(...) expression.

Is there any way to cause both ways to evaluate int.MaxValue + 1 to throw an OverflowException?

EDIT: Well, either tell me if there is a way, or give me a better way to do this (please).

The reason I think I need this is because I have code like:

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)));

I want this to print int: ... if a + b is in the int range, and long: ... if the small-integer addition overflows. Is there a way to do this that is better than simply changing every single Action (of which I have many)?

4

There are 4 answers

1
Tylerflick On BEST ANSWER

To be short, no it is not possible for checked blocks or expressions to have dynamic scope. If you want to apply this in the entirety of your code base you should look to adding it to your compiler options.

Checked expressions or checked blocks should be used where the operation is actually happening.

    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);
    }
0
Akash Kava On

You could use Expression Tree & modify it to introduce Checked for math operator & execute it. This sample is not compiled and tested, you will have to tweak it little more.

   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;
           }
   }
3
lukegravitt On

You shouldn't catch exceptions as part of the natural flow of your program. Instead, you should anticipate the problem. There are quite a few ways you can do this, but assuming you just care about int and long and when the addition overflows:

EDIT: Using the types you mention below in your comment instead of int and long:

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

This makes an assumption that you have a constructor for RFBigInt that promotes a RFSmallInt. This should be trivial as BigInteger has that same for long. There is also an explicit cast from BigInteger to long that you can use to "demote" the value if it is does not overflow.

2
flindeberg On

An exception should be an exception, not the usual program flow. But lets not care about that for now :)

The direct answer to you question I believe is no, but you can always work yourself around the problem. I'm posting a small part of some of the ninja stuff I made when implementing unbounded integers (in effect a linked list of integers) which could help you.

This is a very simplistic approach for doing checked addition manually if performance is not an issue. Is quite nice if you can overload the operators of the types, ie you control the types.

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();
}

Example with your own types:

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; }
}

As I stated earlier, you will lose performance, but gain the exceptions.