C#中的'x Is int'和'x Is int'有区别吗?

C#中的'x Is int'和'x Is int'有区别吗?,c#,types,nullable,cil,boxing,C#,Types,Nullable,Cil,Boxing,vs .method私有隐藏实例bool M2(对象o)cil托管{ .maxstack 8 IL_0000:ldarg.1 IL_0001:isinst valuetype[mscorlib]系统。可为空`1 IL_0006:ldnull IL_0007:cgt.un IL_0009:ret } 正如您所看到的,o is T?表达式实际上执行了Nullable类型的类型检查,尽管可以为null的类型是由CLR专门处理的,因此C#将装箱的T?值表示为null引用(如果T?没有值)或装箱的T值。

vs

.method私有隐藏实例bool M2(对象o)cil托管{
.maxstack 8
IL_0000:ldarg.1
IL_0001:isinst valuetype[mscorlib]系统。可为空`1
IL_0006:ldnull
IL_0007:cgt.un
IL_0009:ret
}
正如您所看到的,
o is T?
表达式实际上执行了
Nullable
类型的类型检查,尽管可以为null的类型是由CLR专门处理的,因此C#将装箱的
T?
值表示为
null
引用(如果
T?
没有值)或装箱的
T
值。似乎不可能在纯C或者甚至C++/CLI中获得可为空的
box
类型(因为运行时处理
box
操作码以支持此“
T”
=>
T
box/
null
”装箱)

我是否遗漏了一些东西或
o是T?
实际上相当于
o是T
在C#?

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

7.10.10
操作员

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

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

  • 如果
    E
    是方法组或空文本,如果
    E
    的类型是引用类型或可空类型,并且E的值为空,则结果为假

  • 否则,让
    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
      是相同的类型,则结果为真
    • 否则,结果为假

考虑这种通用方法:

.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
}
static bool Is(对象arg)
{
返回arg为T;
}
这个方法的关键部分被编译成
isinst!!T
。现在您希望
Is(arg)
的行为方式与
arg Is int完全相同?
,不是吗?为了确保这种精确的一致性,C#编译器必须在所有情况下发出相同的CIL,并让CLR承担处理可空类型的负担

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

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

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


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

对于这样一个令人困惑的问题,这是很多赞成票。你到底在问什么?你的标题的答案很明显:IL表明事实上存在差异。但你的问题似乎是问你是否可以将
可空的
对象框起来。请修正你的问题,以便真正清楚你在尝试什么为了找出答案,我在问“
o是T吗?
在C#中是否实际上等同于
o是T
”,这一点非常清楚:)也许你在寻找其中一种:,,或者以什么方式“实际上等同”?在你看来,什么形式的等同是不“实际的”?我不认为
M2()
将永远返回
true
,因此对我来说它似乎不实用,也不是等效的。您是否对C中的
is
感到惊讶?对于这两种类型,在CIL中编译为
isinst
,其基础是
null
是由CLR专门处理的?这正是它被编译的原因
.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
}
static bool Is<T>(object arg)
{
    return arg is T;
}