I just stumbled into a System.Decimal
oddity once more and seek an explaination.
When casting a value of type System.Decimal
to some other type (i. e. System.Int32
) the checked
keyword and the -checked
compiler option seem to be ignored.
I've created the following test to demonstrate the situation:
public class UnitTest
{
[Fact]
public void TestChecked()
{
int max = int.MaxValue;
// Expected if compiled without the -checked compiler option or with -checked-
Assert.Equal(int.MinValue, (int)(1L + max));
// Unexpected
// this would fail
//Assert.Equal(int.MinValue, (int)(1M + max));
// this succeeds
Assert.Throws<OverflowException>(() => { int i = (int)(1M + max); });
// Expected independent of the -checked compiler option as we explicitly set the context
Assert.Equal(int.MinValue, unchecked((int)(1L + max)));
// Unexpected
// this would fail
//Assert.Equal(int.MinValue, unchecked((int)(1M + max)));
// this succeeds
Assert.Throws<OverflowException>(() => { int i = unchecked((int)(1M + max)); });
// Expected independent of the -checked compiler option as we explicitly set the context
Assert.Throws<OverflowException>(() => { int i = checked((int)(1L + max)); });
// Expected independent of the -checked compiler option as we explicitly set the context
Assert.Throws<OverflowException>(() => { int i = checked((int)(1M + max)); });
}
}
All my research unitl now didn't lead to a proper explaination for this phenomenon or even some misinformation claiming that it should work. My research already included the C# specification
Is there anybody out there who can shed some light on this?
The checked
context relates to IL emitted from your code - it basically changes the opcode used for those math operations from the unchecked version to the checked version. It can't do that for decimal
because decimal
isn't a primitive, and has no direct opcodes: all the arithmetic operations are pre-built in custom operators, exactly like they would be if you added your own struct MyType
and added operators for it. So: it would all depend on whether the custom operators defined by decimal
choose to detect and throw OverflowException
or not, in that code. Which you don't control, and can't influence in your build.
It is the decimal
type that provides the decimal
<===> int
conversions. By the time it gets back to your code - where the checked
keyword could have an effect - it is already either an int
or an exception has been thrown.
The C# custom operator support does not extend to allowing you to add separate checked / unchecked operator implementations, sadly.
C# specification (section 12.7.14 The checked and unchecked operators) contains list of affected operators and statements. Operators in your test aren't in the list:
The following operations are affected by the overflow checking context established by the
checked
andunchecked
operators and statements:
- The predefined
++
and--
operators (§12.7.10 and §12.8.6), when the operand is of an integral or enumtype.- The predefined
-
unary operator (§12.8.3), when the operand is of an integral type.- The predefined
+
,-
,*
, and/
binary operators (§12.9), when both operands are of integral or enumtypes.- Explicit numeric conversions (§11.3.2) from one integral or enumtype to another integral or enumtype, or from
float
ordouble
to an integral or enumtype.
The CLR offers IL instructions for simple arithmetic operations like add
(addition), sub
(subtraction), mul
(multiplication), div
(division).
For example lets take an add
instruction, which adds two values together. The add
instruction performs no overflow checking, but there is instruction called add.ovf
, which also adds two values together, but will throw an OverflowException
if an overflow occurs.
So when you are using checked
operator, statement or compiler switch, it will use "overflow checking" version of add
instruction (add.ovf
).
Remember this only works for a "Primitive Types".
But with decimal
s things are little different. decimal
type is not considered as a primitive type by CLR (although programming languages like c# or visual basic does), which means that CLR does not have IL instructions that know how to manipulate decimal
value. If you look for a decimal
type in .NET Framework SDK Documantation or on Source Code in ReferenceSources - you will notice, that there is methods called Add, Subtract, Multiply, Divide, etc..
and operator overload methods for +, -, *, /, etc
.
When you compile code that uses decimal
, the compiler will generate code to call decimal
members to perform the actual operation. Also, because there are no IL instructions for manipulating decimal
values, the checked/unchecked
operators/statements/compiler switches have no effect. Operations with decimal
values will always throw an OverflowException
if the operation can't be performed safely.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With