Reputation: 6737
class C<T> where T : struct {
bool M1(object o) => o is T;
bool M2(object o) => o is T?;
}
The two methods above seems to behave equally, both when passing null
reference or boxed T
value. However, the generated MSIL code is a bit different:
.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
}
As you may see, the o is T?
expression actually performs type check for Nullable<T>
type, despite the fact that nullable types are specially handled by CLR so that C# represents boxed T?
value as null
reference (if T?
has no value) or boxed T
value. It seems impossible to get box of Nullable<T>
type in pure C# or maybe even in C++/CLI (since runtime handles box
opcode to support this "T?
=> T
box / null
" boxing).
Am I missing something or o is T?
is practically equivalent to o is T
in C#?
Upvotes: 11
Views: 243
Reputation: 13187
Consider this generic method:
static bool Is<T>(object arg)
{
return arg is T;
}
The crucial part of this method gets compiled to isinst !!T
. Now you would expect Is<int?>(arg)
to behave exactly the same way as arg is int?
, wouldn't you? To ensure this exact consistency, the C# compiler must emit the same CIL in all the cases and let the burden of handling nullable types lie on the CLR.
The behaviour of the CLR can be viewed in the coreclr source code on GitHub: IsInst, ObjIsInstanceOf. As you can see in the second function, if the type is a nullable representation of the argument type, it returns true.
allow an object of type T to be cast to Nullable (they have the same representation)
Yes, the current behaviour of these instructions is the same, so changing is T?
to is T
won't make any difference (even for null
argument), but to cope with any possible future changes in the CLR, the C# compiler cannot make that decision (although the probability of isinst
behaviour changed is close to zero).
Nullable types are really a wondrous thing in .NET, especially due to their particular handling in the CLR, despite the fact that they have no special syntax in CIL (for compatibility). There is really no normal way of boxing a nullable type to its actual type and not the underlying one, as it would raise inconsistencies in casts and checks (is null reference equal to a boxed nullable type null or not?). However, you can trick the CLR into thinking you give it a boxed nullable type (not that you should).
Upvotes: 1
Reputation: 32576
According to the spec (emphasis mine), in E is T
, non-nullable value types of T
and corresponding nullable types are handled the same way:
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.
Upvotes: 7