example - remarks c#
¿Por qué se ''implementa'' como ''como''? (10)
Dado que este es un caso de uso muy natural (si no sabes lo as
realmente hace),
if (x is Bar) {
Bar y = x as Bar;
something();
}
es efectivamente equivalente (es decir, el CIL generado por el compilador del código anterior será equivalente) a:
Bar y = x as Bar;
if (y != null) {
y = x as Bar; //The conversion is done twice!
something();
}
EDITAR:
Supongo que no había dejado clara mi pregunta. Nunca escribiría el segundo fragmento ya que, por supuesto, es redundante. Afirmo que el CIL generado por el compilador al compilar el primer fragmento de código es equivalente al segundo fragmento, que es redundante. Preguntas: a) ¿Es esto correcto? b) Si es así, ¿por qué is
implementa así?
Esto se debe a que el primer fragmento es mucho más claro y bonito que el que está realmente bien escrito.
Bar y = x as Bar;
if (y != null) {
something();
}
CONCLUSIÓN:
Optimizar el caso es / no es responsabilidad del compilador, sino de los JIT.
Además, al igual que con un chequeo nulo, tiene menos instrucciones (y menos costosas) que las dos alternativas ( is
and as
and is
y cast
).
Apéndice:
CIL para como con nullcheck (.NET 3.5):
L_0001: ldarg.1
L_0002: isinst string
L_0007: stloc.0
L_0008: ldloc.0
L_0009: ldnull
L_000a: ceq
L_000c: stloc.1
L_000d: ldloc.1
L_000e: brtrue.s L_0019
L_0011: ldarg.0
L_0019: ret
CIL para is y cast (.NET 3.5):
L_0001: ldarg.1
L_0002: isinst string
L_0007: ldnull
L_0008: cgt.un
L_000a: ldc.i4.0
L_000b: ceq
L_000d: stloc.1
L_000e: ldloc.1
L_000f: brtrue.s L_0021
L_0012: ldarg.1
L_0013: castclass string
L_0018: stloc.0
L_0019: ldarg.0
L_0021: ret
CIL para es y como (.NET 3.5):
L_0001: ldarg.1
L_0002: isinst string
L_0007: ldnull
L_0008: cgt.un
L_000a: ldc.i4.0
L_000b: ceq
L_000d: stloc.1
L_000e: ldloc.1
L_000f: brtrue.s L_0021
L_0012: ldarg.1
L_0013: isinst string
L_0018: stloc.0
L_0019: ldarg.0
L_0021: ret
Estos se han editado por falta (declaraciones de métodos, nops y llamadas a algo () eliminadas).
a) es esto correcto
Sí, aunque lo hubiera dicho de otra manera. Usted está diciendo que "es" es un azúcar sintáctico para la comprobación seguida por nula. Lo habría dicho de otra manera: que "as" es un azúcar sintáctico para "verificar la implementación de tipos, emitir si es correcto, nulo si falla".
Es decir, estaría más inclinado a decir
if (x is Bar) {
Bar y = x as Bar;
something();
}
es efectivamente equivalente a
if (x is Bar) {
Bar y = (x is Bar) ? (Bar)x : (Bar) null;
something();
}
Vea, usted quiere definir "como" en términos de "es", no al revés. La pregunta realmente debería ser "¿por qué se implementa como está?" :-)
b) Si es así, ¿por qué se implementa así?
Porque eso es una correcta implementación de la especificación .
Creo que no estoy siguiendo tu línea de pensamiento aquí. ¿Hay algo mal con esa implementación? ¿Cómo preferirías que fuera implementado? Tienes a tu disposición las instrucciones "isinst" y "castclass"; describe el código para tu programa que te gustaría ver.
Bueno, la instrucción IL que está disponible (isinst) devolverá un objeto del tipo apropiado, o nulo si tal conversión no es posible. Y no lanza una excepción si la conversión no es posible.
Dado esto, tanto "es" como "como" son triviales de implementar. No diría que "es" se implementa como "como" en este caso, solo que la instrucción de IL subyacente permite que ambos ocurran. Ahora, por qué el compilador no puede optimizar el "es" seguido de "como" en una sola llamada isinst, eso es otro asunto. Probablemente, en este caso, está relacionado con el alcance de la variable (aunque en el momento en que este sea IL, el alcance no existe realmente)
Editar
Pensándolo bien, no puede optimizar "es" seguido de "como" en una sola llamada isinst, sin saber que la variable en discusión no está sujeta a actualización desde otros subprocesos.
Suponiendo que x es una cadena:
//Thread1
if(x is string)
//Thread2
x = new ComplexObject();
//Thread1
y = x as string
Aquí, y debería ser nulo.
El alcance de ''y'' se reduce si coloca la declaración dentro del bucle.
Quien lo haya escrito probablemente prefiera lanzar ''x como T'' más que ''(T) x'', y quería limitar el alcance de ''y''.
En primer lugar, no estoy de acuerdo con su premisa de que este es un caso de uso más típico. Puede ser su enfoque favorito, pero el enfoque idiomático es el estilo "como + comprobación nula":
Bar y = x as Bar;
if (y != null) {
something();
}
Como ha encontrado, el enfoque "es" requiere el extra "como" o un reparto, por lo que, según mi experiencia, el "como" con comprobación nula es la forma estándar de hacerlo.
No veo nada ofensivo en este enfoque "as", personalmente no creo que sea más desagradable a la vista que cualquier otro código.
En cuanto a su pregunta real, ¿por qué se implementa la palabra clave en términos de la palabra clave as
No tengo idea, pero me gusta el juego de palabras en su pregunta :) Sospecho que ninguna de las dos se implementa en términos de la otra, pero La herramienta (supongo que Reflector) que usaste para generar C # a partir de la IL interpretó la IL en términos de as
.
En su ejemplo, el uso de as
es redundante de todos modos. Como ya sabes que x is Bar
, deberías usar un molde:
if (x is Bar)
{
Bay y = (Bar)x;
}
Alternativamente, convierte usando as
y simplemente comprueba si hay un valor nulo:
Bar y = x as Bar;
if (y != null)
{
}
No harás un segundo y = x as Bar;
, porque tu ya tienes y que es bar.
Podrías escribir el código ahora como
DoIfOfType<Bar>(possibleBar, b => b.something())
Yo diría que fue un poco más claro, pero no tan rápido sin magia real del compilador.
Según la entrada del blog ¿Cuántos pases? por Eric Lippert que es un pase de compilación. Citar:
Luego ejecutamos un pase de optimización que reescribe trivial "es" y "como" operadores.
Entonces tal vez es por eso que estás viendo el mismo CIL generado para ambos fragmentos de código.
Sospecho fuertemente que es más rápido que como y no requiere una asignación. Entonces, si x es rara vez Bar, entonces el primer fragmento es bueno. Si x es mayormente Barra, entonces se recomendaría una, ya que no se requiere un segundo lanzamiento. Depende del uso y circunstancias del código.
Te olvidaste de los tipos de valor. P.ej:
static void Main(string[] args)
{
ValueType vt;
FooClass f = vt as FooClass;
}
private class FooClass
{
public int Bar { get; set; }
}
No se compilará, ya que los tipos de valor no se pueden convertir de esta manera.