El operador “is” en C#devuelve resultados inconsistentes
.net (2)
Me gustaría usar el operador "is" en C # para verificar el tipo de tiempo de ejecución de una instancia de objeto. Pero no parece funcionar como esperaba.
Digamos que tenemos tres conjuntos A1, A2 y A3 que contienen solo una clase.
A1:
public class C1
{
public static void Main()
{
C2 c2 = new C2();
bool res1 = (c2.c3) is C3;
bool res2 = ((object)c2.c3) is C3;
}
}
A2:
public class C2
{
public C3 c3 = new C3();
}
A3:
public class C3
{
}
A1 necesita hacer referencia a A2 y A3.
A2 necesita hacer referencia a A3.
Después de ejecutar Main (), res1 y res2 se establecen en true como se esperaba. El problema se produce cuando comienzo a la versión A3 como ensamblado con nombre seguro y hago A1 para hacer referencia a una versión y A2 para hacer referencia a otra versión de A3 (el código fuente de A3 sigue siendo el mismo). Por cierto el compilador permite esto solo si la versión de A3 referenciada por A2 es inferior o igual a la versión de A3 referenciada por A1. El resultado de este programa ahora es diferente (res1 = true, res2 = false).
¿Es correcto este comportamiento? ¿No deberían ser ambos falsos (o quizás verdad)?
De acuerdo con la especificación C # 5.0 (capítulo 7.10.10), tanto res1 como res2 deberían terminar con el mismo valor. El operador "es" siempre debe considerar el tipo de tiempo de ejecución de la instancia.
En el código IL que puedo ver para res1, el compilador tomó la decisión de que las dos clases de C3 provenientes de diferentes ensamblajes A3 son iguales y emitieron el código sin isinst la comprobación de instrucciones contra nulo solamente. Para res2, el compilador ha agregado la instrucción isinst que pospone la decisión para el tiempo de ejecución. Parece que el compilador de C # tiene una regla diferente sobre cómo resolver esto que el tiempo de ejecución de CLR.
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 36 (0x24)
.maxstack 2
.locals init ([0] class [A2]C2 c2,
[1] bool res1,
[2] bool res2)
IL_0000: nop
IL_0001: newobj instance void [A2]C2::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldfld class [A3]C3 [A2]C2::c3
IL_000d: ldnull
IL_000e: ceq
IL_0010: ldc.i4.0
IL_0011: ceq
IL_0013: stloc.1
IL_0014: ldloc.0
IL_0015: ldfld class [A3]C3 [A2]C2::c3
IL_001a: isinst [A3_3]C3
IL_001f: ldnull
IL_0020: cgt.un
IL_0022: stloc.2
IL_0023: ret
} // end of method C1::Main
¿Podría ser simplemente una compensación para una implementación más rápida y optimizada sin utilizar isinst (teniendo en cuenta la advertencia del compilador)?
La posible opción para evitar esto es la redirección vinculante (como lo sugiere la advertencia) pero no puedo usar eso ya que las versiones no siempre son compatibles con versiones anteriores (aunque la clase C3 siempre lo es). Cambiar la referencia en A2 tampoco es una opción para mí.
EDITAR: Como parece, la solución más fácil es convertir siempre al objeto para obtener el resultado correcto.
De todos modos, sería interesante saber si se trata de un error en el compilador de C # (y posiblemente informar de ello a MS) o no es un error en sí mismo (ya que el compilador identifica un problema e informa de una advertencia), aunque podría generar un código IL correcto.
Desafortunadamente, no tengo una respuesta a por qué el primer resultado es verdadero. Sin embargo, si la especificación dice que is
supone que está basada en el tipo de tiempo de ejecución, Panagiotis es correcta; Los tipos son diferentes y ambos deben devolver falso. GetType () y typeof se comportan como is
debido.
var res3 = c2.c3.GetType() == typeof(C3); // is false
var res4 = ((object)c2.c3).GetType() == typeof(C3); // is false
var localC3 = new C3();
var res5 = localC3 is C3; // is true
var res6 = ((object)localC3).GetType() == typeof(C3); // is true
Mi reacción inmediata sería deshacerme del objeto arrojado, ya que parece funcionar como usted quiere.
Sin embargo, como eso puede cambiar si se arregla. Podrías recurrir a lo siguiente. Dado que su código fue compilado contra ensamblajes firmados, las personas no podrán sustituir un ensamblaje falso.
var res7 = c3.GetType().FullName == typeof(C3).FullName
Con suerte, algo de esto ayuda.
Su problema es que la ecuación de res1
se compila como true
por el compilador de C # (como lo muestra la IL). Sin embargo, res2
está realizando el análisis correcto, ya que lo está haciendo en tiempo de ejecución (cada vez que lanza un object
para object
a C # a volver a las operaciones de tiempo de ejecución para la mayoría de las cosas).
Por lo tanto, parece que el compilador asume que los tipos son los mismos (es probable que no valide la versión de la DLL constituyente).
La única solución que viene a la mente fácilmente es cambiar el alias de uno de ellos y ver si la calificación de qué C3 está hablando ayuda.