【问题标题】:Why does System.Decimal ignore checked/unchecked context为什么 System.Decimal 会忽略已检查/未检查的上下文
【发布时间】:2018-10-24 17:01:14
【问题描述】:

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

有没有人可以解释一下?

【问题讨论】:

    标签: c# .net


    【解决方案1】:

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

    decimal 类型提供了decimal int 转换。当它返回到 您的 代码时 - checked 关键字可能会产生影响 - 它已经是 int 或已引发异常。

    遗憾的是,C# 自定义运算符支持并未扩展到允许您添加单独的选中/未选中运算符实现。

    【讨论】:

    • 操作的默认值是什么? checkedunchecked?
    • @Bharadwaj 默认是:无论构建告诉默认是什么。 “默认默认值”(如果您明白我的意思......)是“未选中”。如果您使用的是 Visual Studio,则此设置位于项目属性的“构建”选项卡上的“高级...”按钮中,标记为“检查算术上溢/下溢”。如果您在 C# 的某个区域周围使用 checkedunchecked 关键字:那将胜过其他一切。它是 csproj 文件中的 &lt;CheckForOverflowUnderflow&gt; 键,或者使用 csc 时为 /checked
    【解决方案2】:

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

    以下操作受checkedunchecked 运算符和语句:

    • 当操作数是整数或枚举类型时,预定义的 ++-- 运算符(§12.7.10 和 §12.8.6)。
    • 预定义的 - 一元运算符(第 12.8.3 节),当操作数为整数类型时。
    • 当两个操作数都是整数或枚举类型时,预定义的 +-*/ 二元运算符(第 12.9 节)。
    • 从一个整数或枚举类型到另一个整数或枚举类型,或从 floatdouble 到一个整数或枚举类型的显式数字转换 (§11.3.2)。

    【讨论】:

    • 你是对的 - 我去过那里但没有看到它:“从一个 整数或枚举类型到另一个整数或枚举类型的显式数字转换(第 11.3.2 节),或从浮点数或双精度数到不包括decimal 的整数或枚举类型"。我仍然更喜欢@marc-gravells 的答案,因为它包含了这背后的原因,这是我的问题的重点。
    【解决方案3】:

    CLR 为简单的算术运算提供 IL 指令,例如 add(加法)、sub(减法)、mul(乘法)、div(除法)。

    例如,让我们采用add 指令,它将两个值相加。 add 指令不执行溢出检查,但有一条指令称为 add.ovf,它也将两个值相加,但如果发生溢出则会抛出 OverflowException

    因此,当您使用checked 运算符、语句或编译器开关时,它将使用add 指令的“溢出检查” 版本(add.ovf)。

    请记住,这只适用于“原始类型”

    但是decimals 的情况略有不同。 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

    【讨论】:

      猜你喜欢
      • 2022-08-15
      • 1970-01-01
      • 2013-03-12
      • 1970-01-01
      • 1970-01-01
      • 2019-10-03
      • 1970-01-01
      • 1970-01-01
      • 2016-10-16
      相关资源
      最近更新 更多