为什么System.Decimal忽略已选中/未选中的上下文

时间:2018-01-22 11:24:41

标签: c# .net

我再一次偶然发现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

有没有人可以对此有所了解?

3 个答案:

答案 0 :(得分:28)

checked上下文与从代码中发出的IL相关 - 它基本上将用于这些数学运算的操作码从未经检查的版本更改为已检查的版本。它不能为decimal执行此操作,因为decimal 不是原始,并且没有直接操作码:所有算术运算都是在自定义运算符中预先构建的,就像你添加自己的struct MyType并为其添加运算符一样。所以:这完全取决于decimal定义的自定义运算符是否选择检测并抛出OverflowException,而不是在该代码中。你无法控制,也不能影响你的构建。

decimal类型提供decimal&lt; ===&gt;转化int次。当它返回到您的代码时 - checked关键字可能会产生影响 - 它已经是int或者已经抛出异常。

C#自定义运算符支持不会扩展到允许您添加单独的已检查/未检查的运算符实现,遗憾的是。

答案 1 :(得分:2)

C#规范(第12.7.14节“已检查和未检查的运算符”)包含受影响的运算符和语句的列表。测试中的操作员不在列表中:

  

以下操作受checkedunchecked建立的溢出检查上下文的影响   ++运算符和语句:

     
      
  • 预定义的---运算符(第12.7.10节和第12.8.6节),当操作数是整数或枚举类型时。
  •   
  • 预定义的+一元运算符(第12.8.3节),当操作数是整数类型时。
  •   
  • 预定义的-*/float二元运算符(第12.9节),当两个操作数都是整数或enumtypes时。
  •   
  • 显式数字转换(第11.3.2节),从一个整数或枚举类型到另一个整数或枚举类型,或从doublevoid 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