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
}
VS
.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<T>
类型执行类型检查,尽管CLR专门处理可空类型,因此C#表示盒装T?
值为null
引用(如果T?
没有值)或框T
值。似乎不可能在纯C#或甚至在C ++ / CLI中获得Nullable<T>
类型的框(因为运行时处理box
操作码以支持此“T?
=&gt; T
box / null
“拳击”。
我是否遗漏了某些内容,或者o is T?
实际上等同于C#中的o is T
?
答案 0 :(得分:7)
根据规范(强调我的),在E is T
中,T
的非可空值类型和相应的可空类型以相同的方式处理:
7.10.10
is
运算符
is
运算符用于动态检查对象的运行时类型是否与给定类型兼容。操作E is T
的结果,其中E
是表达式而T
是一个类型,是一个布尔值,指示E
是否可以成功转换为类型{{1通过引用转换,装箱转换或拆箱转换。在用类型参数替换所有类型参数之后,操作如下评估:
如果
T
是匿名函数,则会发生编译时错误如果
E
是方法组或空文字,如果E
的类型是引用类型或可空类型且E的值为null,则结果为假否则,让
E
代表D
的动态类型,如下所示:
- 如果
E
的类型是引用类型,E
是D
的实例引用的运行时类型。如果
E
的类型是可空类型,则E
是该可空类型的基础类型。如果
D
的类型是不可为空的值类型,则E
的类型为D
。操作的结果取决于
E
和D
,如下所示:
- 如果
T
是引用类型,如果T
和D
是相同的类型,如果T
是引用类型和隐式引用转换,则结果为true从D
到D
存在,或者如果T
是值类型,则存在从D
到D
的装箱转换。- 如果
T
是可以为空的类型,则T
是基础类型D
时结果为真。< / LI>- 如果
T
是不可为空的值类型,则T
和D
属于同一类型时结果为真- 否则,结果为假。
答案 1 :(得分:1)
考虑这种通用方法:
static bool Is<T>(object arg)
{
return arg is T;
}
此方法的关键部分将编译为isinst !!T
。现在您希望Is<int?>(arg)
的行为与arg is int?
完全相同,不是吗?为了确保这种精确的一致性,C#编译器必须在所有情况下发出相同的CIL,并且让处理可空类型的负担落在CLR上。
可以在GitHub上的coreclr源代码中查看CLR的行为:IsInst,ObjIsInstanceOf。正如您在第二个函数中看到的,如果类型是参数类型的可空表示,则返回true。
允许将类型为T的对象强制转换为Nullable(它们具有相同的表示)
是的,这些说明的当前行为是相同的,因此将is T?
更改为is T
不会产生任何差异(即使对于null
参数),但要应对对于CLR中任何可能的未来更改,C#编译器无法做出该决定(尽管isinst
行为更改的概率接近于零)。
Nullable类型在.NET中确实是一个奇妙的东西,特别是由于它们在CLR中的特殊处理,尽管它们在CIL中没有特殊的语法(为了兼容性)。实际上没有正常的方法可以将可空类型装入其实际类型而不是基础类型,因为它会引起强制转换和检查中的不一致(空引用等于盒装可空类型是否为null?)。但是,你可以trick CLR认为你给它一个盒装的可空类型(不是你应该的)。