válido specification significado programa msil medicina language español detectó common abreviatura c# clr

c# - specification - Reparto vs ''como'' operador revisado



common language specification (5)

Sé que ya hay varios mensajes relacionados con la diferencia entre los lanzadores y el operador as . Todos en su mayoría reiteran los mismos hechos:

  • El operador as no lanzará, pero devolverá el null si falla el lanzamiento.
  • En consecuencia, el operador as solo trabaja con tipos de referencia
  • El operador as no utilizará operadores de conversión definidos por el usuario

Las respuestas tienden entonces a debatir sin cesar sobre cómo usar o no usar lo uno o lo otro y las ventajas y desventajas de cada uno, incluso su desempeño (lo que no me interesa en absoluto).

Pero hay algo más en el trabajo aquí. Considerar:

static void MyGenericMethod<T>(T foo) { var myBar1 = foo as Bar; // compiles var myBar2 = (Bar)foo; // does not compile (''Cannot cast expression of // type ''T'' to type ''Bar'') }

Por favor, no importa si este ejemplo obviamente contrario es una buena práctica o no. Mi preocupación aquí es la disparidad muy interesante entre los dos en que el reparto no se compilará mientras as hace. Realmente me gustaría saber si alguien podría arrojar algo de luz sobre esto.

Como se observa a menudo, el operador as ignora las conversiones definidas por el usuario, pero en el ejemplo anterior, es claramente el más capaz de los dos. Tenga en cuenta que as lo as respecta al compilador, no hay conexión conocida entre el tipo T y Bar (desconocido en tiempo de compilación). El reparto es enteramente ''run-time''. ¿Debemos sospechar que el reparto se resuelve, total o parcialmente, en tiempo de compilación y no as operador?

Por cierto, la adición de una restricción de tipo, como es lógico, corrige la conversión, por lo tanto:

static void MyGenericMethod<T>(T foo) where T : Bar { var myBar1 = foo as Bar; // compiles var myBar2 = (Bar)foo; // now also compiles }

¿Por qué el operador as compila y el elenco no?


¿Debemos sospechar que el reparto se resuelve, total o parcialmente, en tiempo de compilación y no como operador?

Usted mismo dio la respuesta al comienzo de su pregunta: "El operador as no usará operadores de conversión definidos por el usuario"; mientras tanto, el cast lo hace , lo que significa que debe encontrar esos operadores ( o su ausencia ) en el momento de la compilación.

Tenga en cuenta que, en lo que respecta al compilador, no hay conexión conocida entre el tipo T y Bar (desconocido en tiempo de compilación).

El hecho de que el tipo T sea desconocido significa que el compilador no puede saber si no hay conexión entre él y Bar.

Tenga en cuenta que (Bar)(object)foo no funciona, porque ningún tipo puede tener un operador de conversión a Objeto [ya que es la clase base de todo], y se sabe que la conversión de objeto a Barra no tiene que tratar con una conversión operador.


El compilador no sabe cómo generar código que funcione para todos los casos.

Considere estas dos llamadas:

MyGenericMethod(new Foo1()); MyGenericMethod(new Foo2());

ahora asuma que Foo1 contiene un operador de conversión que puede convertirlo en una instancia de Bar , y que Foo2 desciende de Bar lugar. Obviamente, el código involucrado dependerá en gran medida de la T real que usted pase.

En su caso particular, usted dice que el tipo ya es un tipo de Bar por lo que, obviamente, el compilador solo puede hacer una conversión de referencia, porque sabe que es seguro, que no se está realizando ninguna conversión o que es necesario.

Ahora, as conversión es más "exploratoria", no solo no considera las conversiones de los usuarios, sino que explícitamente permite el hecho de que la conversión no tiene sentido, por lo que el compilador deja que se deslice.


Es una cuestión de tipo seguridad.
Cualquier T no se puede convertir a una Bar , pero cualquier T se puede "ver" as una Bar ya que el comportamiento está bien definido incluso si no hay conversión de T a Bar .


La primera compila simplemente porque así es as se define la palabra clave as . Si no se puede lanzar, devolverá null . Es seguro porque la palabra clave as no causa ningún problema de tiempo de ejecución. El hecho de que puede o no haber comprobado que la variante sea nula es otra cuestión.

Piense as como un método TryCast.


Para abordar su primera pregunta: no es solo que el operador as no tenga en cuenta las conversiones definidas por el usuario, sino que es relevante. Lo que es más relevante es que el operador de reparto hace dos cosas contradictorias. El operador de yeso significa:

  1. Sé que esta expresión del tipo Foo en tiempo de compilación será en realidad un objeto de la barra de tipo runtime. Compilador, les estoy contando este hecho ahora para que puedan usarlo. Por favor genere el código asumiendo que estoy en lo correcto; si soy incorrecto, entonces puedes lanzar una excepción en el tiempo de ejecución.

  2. Sé que esta expresión del tipo Foo en tiempo de compilación será realmente del tipo Foo en tiempo de ejecución. Existe una forma estándar de convertir algunas o todas las instancias de Foo en una instancia de Bar. Compilador, genere dicha conversión, y si resulta en tiempo de ejecución que el valor que se está convirtiendo no es convertible, entonces lance una excepción en tiempo de ejecución.

Esos son los opuestos . Buen truco, tener un operador que haga cosas opuestas.

El operador as en contraste solo tiene el primer sentido. Y as solo lo hacen las conversiones de boxeo , unboxing y preservación de la representación . Un reparto puede hacer todo eso más conversiones adicionales que cambian la representación. Por ejemplo, la conversión de int a short cambia la representación de un entero de cuatro bytes a un entero de dos bytes.

Es por eso que los modelos "crudos" no son legales en genéricos sin restricciones; porque el compilador no tiene suficiente información para averiguar qué tipo de lanzamiento es: boxeo, desempaquetado, preservación de la representación o cambio de representación. La expectativa de los usuarios es que una conversión en código genérico tiene toda la semántica de una conversión en código más fuertemente tipado, y no tenemos manera de generar ese código de manera eficiente.

Considerar:

void M<T, U>(T t, out U u) { u = (U)t; }

¿Esperas que funcione? Qué código generamos que podemos manejar:

M<object, string>(...); // explicit reference conversion M<string, object>(...); // implicit reference conversion M<int, short>(...); // explicit numeric conversion M<short, int>(...); // implicit numeric conversion M<int, object>(...); // boxing conversion M<object, int>(...); // unboxing conversion M<decimal?, int?>(...); // lifted conversion calling runtime helper method // and so on; I could give you literally hundreds of different cases.

Básicamente, tendríamos que emitir código para la prueba que inició el compilador nuevamente , hizo un análisis completo de las expresiones y luego emitió un nuevo código. Implementamos esa característica en C # 4; se llama "dinámico" y si ese es el comportamiento que desea, puede sentirse libre de usarlo.

No tenemos ninguno de estos problemas con as , porque as solo hace tres cosas. Hace conversiones de boxeo, conversiones de desempaquetado y pruebas de tipo, y podemos generar fácilmente el código que hace esas tres cosas.