class C<T> where T : struct { bool M1(object o) => o is T; bool M2(object o) => o is T?; } Los dos métodos anteriores parecen comportarse por igual, tanto al pasar una referencia null como un valor T encuadrado. Sin embargo, el código MSIL generado es un poco diferente:
.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 }contra
.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 } Como puede ver, la o is T? expresión en realidad realiza una verificación de tipo para el tipo Nullable<T> , a pesar de que CLR maneja especialmente los tipos que aceptan valores NULL para que C# represente la T? valor como referencia null (si T? no tiene valor) o valor T encuadrado. Parece imposible obtener un cuadro de Nullable<T> en C# puro o tal vez incluso en C++/CLI (ya que el tiempo de ejecución maneja el código de operación del box para admitir este cuadro " T? => T box / null ").
¿Me estoy perdiendo algo o o is T? es prácticamente equivalente a o is T en C#?
De acuerdo con la especificación (énfasis mío), en E is T , los tipos de valor no anulables de T y los tipos anulables correspondientes se manejan de la misma manera:
7.10.10 El operador
isEl operador
isse usa para verificar dinámicamente si el tipo de tiempo de ejecución de un objeto es compatible con un tipo dado. El resultado de la operaciónE is T, dondeEes una expresión yTes un tipo, es un valor booleano que indica siEse puede convertir correctamente al tipoTmediante una conversión de referencia, una conversión boxing o una conversión unboxing. La operación se evalúa de la siguiente manera, después de que los argumentos de tipo hayan sido sustituidos por todos los parámetros de tipo:
Si
Ees una función anónima, se produce un error en tiempo de compilaciónSi
Ees un grupo de métodos o el literal nulo, o si el tipo deEes un tipo de referencia o un tipo que acepta valores NULL y el valor de E es nulo, el resultado es falso.De lo contrario, represente
Del tipo dinámico deEde la siguiente manera:
- Si el tipo de
Ees un tipo de referencia,Des el tipo de tiempo de ejecución de la referencia de instancia deE.Si el tipo de
Ees un tipo que acepta valores NULL,Des el tipo subyacente de ese tipo que acepta valores NULL.Si el tipo de
Ees un tipo de valor que no acepta valores NULL,Des el tipo deE.El resultado de la operación depende de
DyTcomo sigue:
- Si
Tes un tipo de referencia, el resultado es verdadero siDyTson del mismo tipo, siDes un tipo de referencia y existe una conversión de referencia implícita deDaT, o siDes un tipo de valor y una conversión boxing deDaTexiste.- Si
Tes un tipo que acepta valores NULL, el resultado es verdadero siDes el tipo subyacente deT.- Si
Tes un tipo de valor que no acepta valores NULL, el resultado es verdadero siDyTson del mismo tipo .- De lo contrario, el resultado es falso.
Considere este método genérico:
static bool Is<T>(object arg) { return arg is T; } La parte crucial de este método se compila en isinst !!T . Ahora esperaría que Is<int?>(arg) se comportara exactamente de la misma manera que arg is int? , ¿no? Para garantizar esta coherencia exacta, el compilador de C# debe emitir el mismo CIL en todos los casos y dejar que la carga de manejar los tipos anulables recaiga en el CLR.
El comportamiento de CLR se puede ver en el código fuente de coreclr en GitHub: IsInst , ObjIsInstanceOf . Como puede ver en la segunda función, si el tipo es una representación anulable del tipo de argumento, devuelve verdadero.
permitir que un objeto de tipo T se convierta en Nullable (tienen la misma representación)
Sí, el comportamiento actual de estas instrucciones es el mismo, entonces, ¿cambiar is T? to is T no hará ninguna diferencia (incluso para un argumento null ), pero para hacer frente a posibles cambios futuros en el CLR, el compilador de C# no puede tomar esa decisión (aunque la probabilidad de que cambie el comportamiento de isinst es cercana a cero).
Los tipos anulables son realmente maravillosos en .NET, especialmente debido a su manejo particular en CLR, a pesar de que no tienen una sintaxis especial en CIL (por compatibilidad). Realmente no hay una forma normal de encajonar un tipo anulable a su tipo real y no al subyacente, ya que generaría inconsistencias en las conversiones y comprobaciones (¿la referencia nula es igual a un tipo anulable encajonado nulo o no?). Sin embargo, puede engañar al CLR para que piense que le da un tipo anulable en caja (no es que deba hacerlo).