在 C# 中,`x is int?` 和 `x is int` 之间有区别吗?
Is there a difference between `x is int?` and `x is int` in C#?
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<T>
类型执行类型检查,尽管可空类型由 CLR 专门处理,因此 C# 表示装箱 T?
值作为 null
参考(如果 T?
没有值)或盒装 T
值。似乎不可能在纯 C# 中甚至在 C++/CLI 中获得 Nullable<T>
类型的框(因为运行时处理 box
操作码来支持这个 "T?
=> T
框/null
”拳击)。
我是不是遗漏了什么或者 o is T?
实际上等同于 C# 中的 o is T
?
根据规范(强调我的),在 E is T
中,T
的不可空值类型和相应的可空类型的处理方式相同:
7.10.10 The is
operator
The is
operator is used to dynamically check if the run-time type of an object is compatible with a given type. The result of the operation E is T
, where E
is an expression and T
is a type, is a boolean value indicating whether E
can successfully be converted to type T
by a reference conversion, a boxing conversion, or an unboxing conversion. The operation is evaluated as follows, after type arguments have been substituted for all type parameters:
If E
is an anonymous function, a compile-time error occurs
If E
is a method group or the null literal, of if the type of E
is a reference type or a nullable type and the value of E is null, the result is false.
Otherwise, let D
represent the dynamic type of E
as follows:
- If the type of
E
is a reference type, D
is the run-time type of the instance reference by E
.
If the type of E
is a nullable type, D
is the underlying type of that nullable type.
If the type of E
is a non-nullable value type, D
is the type of E
.
The result of the operation depends on D
and T
as follows:
- If
T
is a reference type, the result is true if D
and T
are the same type, if D
is a reference type and an implicit reference conversion from D
to T
exists, or if D
is a value type and a boxing conversion from D
to T
exists.
- If
T
is a nullable type, the result is true if D
is the underlying type of T
.
- If
T
is a non-nullable value type, the result is true if D
and T
are the same type.
- Otherwise, the result is false.
考虑这个通用方法:
static bool Is<T>(object arg)
{
return arg is T;
}
此方法的关键部分被编译为 isinst !!T
。现在您会期望 Is<int?>(arg)
的行为方式与 arg is int?
完全相同,不是吗?为了确保这种精确的一致性,C# 编译器必须在所有情况下发出相同的 CIL,并让 CLR 承担处理可空类型的负担。
可以在 GitHub: IsInst, ObjIsInstanceOf 的 coreclr 源代码中查看 CLR 的行为。正如您在第二个函数中看到的那样,如果类型是参数类型的可为 null 的表示形式,则它 returns true.
allow an object of type T to be cast to Nullable (they have the same representation)
是的,这些指令的当前行为是相同的,因此将 is T?
更改为 is T
不会有任何区别(即使对于 null
参数),但为了应对对于 CLR 未来可能发生的任何更改,C# 编译器无法做出该决定(尽管 isinst
行为更改的可能性接近于零)。
可空类型在 .NET 中确实是一件奇妙的事情,特别是由于它们在 CLR 中的特殊处理,尽管它们在 CIL 中没有特殊语法(为了兼容性)。确实没有将可空类型装箱到其实际类型而不是基础类型的正常方法,因为它会导致强制转换和检查中的不一致(空引用是否等于装箱的可空类型是否为 null?)。但是,您可以 trick CLR 认为您给它一个装箱的可空类型(不是您应该的)。
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<T>
类型执行类型检查,尽管可空类型由 CLR 专门处理,因此 C# 表示装箱 T?
值作为 null
参考(如果 T?
没有值)或盒装 T
值。似乎不可能在纯 C# 中甚至在 C++/CLI 中获得 Nullable<T>
类型的框(因为运行时处理 box
操作码来支持这个 "T?
=> T
框/null
”拳击)。
我是不是遗漏了什么或者 o is T?
实际上等同于 C# 中的 o is T
?
根据规范(强调我的),在 E is T
中,T
的不可空值类型和相应的可空类型的处理方式相同:
7.10.10 The
is
operatorThe
is
operator is used to dynamically check if the run-time type of an object is compatible with a given type. The result of the operationE is T
, whereE
is an expression andT
is a type, is a boolean value indicating whetherE
can successfully be converted to typeT
by a reference conversion, a boxing conversion, or an unboxing conversion. The operation is evaluated as follows, after type arguments have been substituted for all type parameters:
If
E
is an anonymous function, a compile-time error occursIf
E
is a method group or the null literal, of if the type ofE
is a reference type or a nullable type and the value of E is null, the result is false.Otherwise, let
D
represent the dynamic type ofE
as follows:
- If the type of
E
is a reference type,D
is the run-time type of the instance reference byE
.If the type of
E
is a nullable type,D
is the underlying type of that nullable type.If the type of
E
is a non-nullable value type,D
is the type ofE
.The result of the operation depends on
D
andT
as follows:
- If
T
is a reference type, the result is true ifD
andT
are the same type, ifD
is a reference type and an implicit reference conversion fromD
toT
exists, or ifD
is a value type and a boxing conversion fromD
toT
exists.- If
T
is a nullable type, the result is true ifD
is the underlying type ofT
.- If
T
is a non-nullable value type, the result is true ifD
andT
are the same type.- Otherwise, the result is false.
考虑这个通用方法:
static bool Is<T>(object arg)
{
return arg is T;
}
此方法的关键部分被编译为 isinst !!T
。现在您会期望 Is<int?>(arg)
的行为方式与 arg is int?
完全相同,不是吗?为了确保这种精确的一致性,C# 编译器必须在所有情况下发出相同的 CIL,并让 CLR 承担处理可空类型的负担。
可以在 GitHub: IsInst, ObjIsInstanceOf 的 coreclr 源代码中查看 CLR 的行为。正如您在第二个函数中看到的那样,如果类型是参数类型的可为 null 的表示形式,则它 returns true.
allow an object of type T to be cast to Nullable (they have the same representation)
是的,这些指令的当前行为是相同的,因此将 is T?
更改为 is T
不会有任何区别(即使对于 null
参数),但为了应对对于 CLR 未来可能发生的任何更改,C# 编译器无法做出该决定(尽管 isinst
行为更改的可能性接近于零)。
可空类型在 .NET 中确实是一件奇妙的事情,特别是由于它们在 CLR 中的特殊处理,尽管它们在 CIL 中没有特殊语法(为了兼容性)。确实没有将可空类型装箱到其实际类型而不是基础类型的正常方法,因为它会导致强制转换和检查中的不一致(空引用是否等于装箱的可空类型是否为 null?)。但是,您可以 trick CLR 认为您给它一个装箱的可空类型(不是您应该的)。