【问题标题】:Is there a difference between `x is int?` and `x is int` in C#?`x is int` 和 C# 中的 `x is int` 之间有区别吗?
【发布时间】:2017-03-07 00:14:46
【问题描述】:
class C<T> where T : struct {
    bool M1(object o) => o is T;
    bool M2(object o) => o is T?;
}

在传递null 引用或装箱T 值时,上述两种方法的行为似乎相同。不过生成的 MSIL 代码有点不同:

.method private hidebysig instance bool M1(object o) cil managed {
    .maxstack 8
    IL_0000: ldarg.1
    IL_0001: isinst !T
    IL_0006: ldnull
    IL_0007: cgt.un
    IL_0009: ret
}

对

.method private hidebysig instance bool M2(object o) cil managed {
    .maxstack 8
    IL_0000: ldarg.1
    IL_0001: isinst valuetype [mscorlib]System.Nullable`1<!T>
    IL_0006: ldnull
    IL_0007: cgt.un
    IL_0009: ret
}

如您所见,o is T? 表达式实际上对 Nullable&lt;T&gt; 类型执行类型检查,尽管 CLR 专门处理可空类型以便 C# 将装箱的 T? 值表示为 null 引用(如果T? 没有值)或装箱的 T 值。似乎不可能在纯 C# 或什至在 C++/CLI 中获得Nullable&lt;T&gt; 类型的框(因为运行时处理box 操作码以支持此“T? => T 框/null”装箱)。

我是否遗漏了什么或者o is T?实际上等同于C#中的o is T?

【问题讨论】:

  • 对于这样一个令人困惑的问题,有很多赞成票。你到底在这里问什么?您的标题的答案很明显:IL 表明实际上存在差异。但是您的问题似乎是在询问您是否可以装箱 Nullable&lt;T&gt; 对象。请解决您的问题,以便您清楚地了解您想要找出的内容。
  • 我在问“o is T? 是否实际上等同于 C# 中的 o is T”,它是尽可能清晰 :)
  • “实际上等效”以什么方式?在您看来,哪种形式的对等不是“实用的”?我认为M2() 永远不会返回true,所以这对我来说似乎不实用,也不等效。
  • 您对 C# 中的 is 被编译为 CIL 中的 isinst 这两种类型感到惊讶吗,因为 Nullable&lt;T&gt; 是由 CLR 专门处理的?这正是它被编译为相同内容的原因,因此 C# 编译器不必处理可为空的恶作剧。

标签: c# types nullable cil boxing


【解决方案1】:

根据规范(强调我的),在E is T 中,T 的不可空值类型和相应的可空类型以相同的方式处理:

7.10.10 is 运算符

is 运算符用于动态检查对象的运行时类型是否与给定类型兼容。操作E is T的结果,其中E是一个表达式,T是一个类型,是一个布尔值,表示E是否可以通过引用转换成功转换为T类型,装箱转换,或拆箱转换。在将类型实参替换为所有类型参数之后,该操作的评估如下:

  • 如果E 是匿名函数,则会发生编译时错误

  • 如果E是方法组或null字面量,如果E的类型是引用类型或可空类型且E的值为null,则结果为false。

  • 否则,让D代表E的动态类型如下:

    • 如果E的类型是引用类型,则D是E的实例引用的运行时类型。
    • 如果E 的类型是可空类型,则D 是该可空类型的基础类型。

    • 如果E的类型是不可为空的值类型,那么D就是E的类型。

  • 运算结果取决于D和T如下:

    • 如果T是引用类型,如果D和T是相同类型,如果D是引用类型并且从D到T的隐式引用转换,则结果为真存在,或者如果D 是一个值类型并且存在从D 到T 的装箱转换。
    • 如果T 是可空类型,则如果D 是T 的基础类型,则结果为真。
    • 如果T 是一个不可为空的值类型,那么如果D 和T 是同一类型,则结果为真。
    • 否则结果为假。

【讨论】:

    【解决方案2】:

    考虑这个通用方法:

    static bool Is<T>(object arg)
    {
        return arg is T;
    }
    

    这个方法的关键部分被编译成isinst !!T。现在您会期望Is&lt;int?&gt;(arg) 的行为方式与arg is int? 完全相同,不是吗?为了确保这种精确的一致性,C# 编译器必须在所有情况下发出相同的 CIL,并将处理可空类型的负担交给 CLR。​​

    CLR 的行为可以在 GitHub 上的 coreclr 源代码中查看:IsInst、ObjIsInstanceOf。如您在第二个函数中所见,如果类型是参数类型的可为空表示,则返回 true。

    允许将 T 类型的对象强制转换为 Nullable(它们具有相同的表示)

    是的,这些指令的当前行为是相同的,因此将 is T? 更改为 is T 不会有任何区别(即使对于 null 参数),但要应对 CLR 中任何可能的未来变化,C# 编译器无法做出该决定(尽管isinst 行为改变的概率接近于零)。

    Nullable 类型在 .NET 中确实是一件奇妙的事情,尤其是由于它们在 CLR 中的特殊处理,尽管事实上它们在 CIL 中没有特殊语法(为了兼容性)。实际上没有正常的方法可以将可空类型装箱为其实际类型而不是底层类型,因为它会在强制转换和检查中引发不一致(空引用是否等于装箱的可空类型 null ?)。但是,您可以 trick CLR 认为您给它一个装箱的可为空类型(不是您应该这样做的)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 2013-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-20
      • 2021-06-21
      相关资源
      最近更新 更多