我再一次偶然发现System.Decimal
奇怪的事情并寻求解释。
将类型System.Decimal
的值转换为某种其他类型(即System.Int32
)时,checked
keyword和-checked
compiler option似乎会被忽略。
我已经创建了以下测试来演示这种情况:
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)); });
}
}
我所有的研究单位现在都没有对这种现象或某些misinformation claiming that it should work进行适当的解释。 我的研究已经包括C# specification
有没有人可以对此有所了解?
答案 0 :(得分:28)
checked
上下文与从代码中发出的IL相关 - 它基本上将用于这些数学运算的操作码从未经检查的版本更改为已检查的版本。它不能为decimal
执行此操作,因为decimal
不是原始,并且没有直接操作码:所有算术运算都是在自定义运算符中预先构建的,就像你添加自己的struct MyType
并为其添加运算符一样。所以:这完全取决于decimal
定义的自定义运算符是否选择检测并抛出OverflowException
,而不是在该代码中。你无法控制,也不能影响你的构建。
decimal
类型提供decimal
&lt; ===&gt;转化int
次。当它返回到您的代码时 - checked
关键字可能会产生影响 - 它已经是int
或者已经抛出异常。
C#自定义运算符支持不会扩展到允许您添加单独的已检查/未检查的运算符实现,遗憾的是。
答案 1 :(得分:2)
C#规范(第12.7.14节“已检查和未检查的运算符”)包含受影响的运算符和语句的列表。测试中的操作员不在列表中:
以下操作受
checked
和unchecked
建立的溢出检查上下文的影响++
运算符和语句:
- 预定义的
--
和-
运算符(第12.7.10节和第12.8.6节),当操作数是整数或枚举类型时。- 预定义的
+
一元运算符(第12.8.3节),当操作数是整数类型时。- 预定义的
-
,*
,/
和float
二元运算符(第12.9节),当两个操作数都是整数或enumtypes时。- 显式数字转换(第11.3.2节),从一个整数或枚举类型到另一个整数或枚举类型,或从
double
或void x(StringBuffer s) { s.append("a"); } StringBuffer y(StringBuffer s) { return new StringBuffer(s.toString()).append("a"); }
到整数或枚举类型。
答案 2 :(得分:1)
CLR为简单的算术运算提供IL指令,例如add
(加法),sub
(减法),mul
(乘法),div
(除法)。
例如,让我们接受一条add
指令,该指令将两个值相加。 add
指令不执行任何溢出检查,但是有一条名为add.ovf
的指令,该指令还将两个值加在一起,但是如果发生溢出,则会抛出OverflowException
。
因此,当您使用checked
运算符,语句或编译器开关时,它将使用add
指令(add.ovf
)的“溢出检查” 版本。
请记住,这仅适用于“原始类型” 。
但是与decimal
的区别不大。 decimal
类型不被CLR视为原始类型(尽管像c#或Visual Basic这样的编程语言也可以),这意味着CLR没有知道如何操纵decimal
值的IL指令。如果您在.NET Framework SDK文档中或在Source Code in ReferenceSources上寻找decimal
类型,您会注意到,有Add, Subtract, Multiply, Divide, etc..
方法和+, -, *, /, etc
的运算符重载方法。
当您编译使用decimal
的代码时,编译器将生成代码来调用decimal
成员以执行实际操作。另外,由于没有用于操纵decimal
值的IL指令,因此checked/unchecked
运算符/语句/编译器开关无效。如果操作不能安全执行,则具有decimal
值的操作将始终抛出OverflowException
。