Динамическое "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; }
}
Как я уже говорил ранее, вы потеряете производительность, но получите исключения.