2011-09-21 9 views
19

Supongamos que tengo un método genérico:¿Por qué el casting proporciona CS0030, mientras que "as" funciona?

T Foo(T x) { 
    return x; 
} 

Hasta aquí todo bien. Pero quiero hacer algo especial si es un Hashtable. (Sé que esto es un ejemplo completamente artificial. Foo() no es un método muy emocionante, ya sea. El juego.)

if (typeof(T) == typeof(Hashtable)) { 
    var h = ((Hashtable)x); // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable' 
} 

Darn. Para ser justos, no puedo decir si esto debería ser legal C# o no. Bueno, ¿y si trato de hacerlo de otra manera?

if (typeof(T) == typeof(Hashtable)) { 
    var h = x as Hashtable; // works (and no, h isn't null) 
} 

Eso es un poco raro. De acuerdo con MSDN, expression as Type es (excepto para evaluar la expresión dos veces) lo mismo que expression is type ? (type)expression : (type)null.

¿Qué ocurre si trato de usar la expresión equivalente de los documentos?

if (typeof(T) == typeof(Hashtable)) { 
    var h = (x is Hashtable ? (Hashtable)x : (Hashtable)null); // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable' 
} 

La única diferencia entre la fundición y documentado as que veo es "el operador as sólo realiza las conversiones de referencia y las conversiones de boxeo". Tal vez necesito decir que estoy usando un tipo de referencia?

T Foo(T x) where T : class { 
    var h = ((Hashtable)x); // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable' 
    return x; 
} 

¿Qué está pasando? ¿Por qué as funciona bien, mientras que el lanzamiento ni siquiera se compila? ¿Debería funcionar el elenco, o el as no funciona, o hay alguna otra diferencia de idioma entre la conversión y as que no se encuentra en estos documentos de MSDN que encontré?

+0

Estoy bastante seguro de que la frase que está citando de MSDN existía antes que los genéricos. –

+0

Aún así, si eso es todo lo que hay ... ¡Interesante pregunta! – Blindy

+0

¿Es esto un engaño, o simplemente relacionado de cerca: http://stackoverflow.com/questions/884315/how-to-up-cast-to-a-generic-object? –

Respuesta

7

El operador de conversión en C# puede:

  • caja/unbox
  • upcast/abatido
  • llamada un operador de conversión definida por el usuario

as Hashtable siempre significa que el segundo.

Al eliminar los tipos de valor con la restricción, ha anulado la opción 1, pero sigue siendo ambigua.


Éstos son los dos "mejores" se aproxima a que tanto el trabajo:

Hashtable h = x as Hashtable; 
if (h != null) { 
    ... 
} 

o

if (x is Hashtable) { 
    Hashtable h = (Hashtable)(object)x; 
    ... 
} 

Las primeras necesidades sólo un ensayo de tipo, por lo que es muy eficiente. Y el optimizador JIT reconoce el segundo y lo trata como el primero (al menos cuando se trata de tipos no genéricos, no estoy seguro sobre este caso en particular).

+0

Entonces, ¿está diciendo que la conversión falla porque podría haber operadores de conversión en el tipo? No digo que estás equivocado pero ... el compilador ya sabe que el tipo genérico es al menos un 'objeto' (la parte' T: class' en el OP), así que si puedes lanzar 'object's blind, no debería también puedes hacerlo para tipos genéricos? – Blindy

+1

Esta explicación tiene sentido para mí. También significa que los documentos de MSDN no estaban equivocados, sino que se escribieron de forma confusa: "A es lo mismo que B, excepto en el caso C. Tenga en cuenta que también es diferente en el modo D." – Ken

+0

@Blindy: Downcast tiene prioridad sobre la conversión definida por el usuario, cuando ambos coinciden. Y dado que cada objeto se deriva de 'objeto', un molde de' objeto' siempre coincide con downcast. Por ejemplo, si este código se escribiera como '(Hashtable) (object) x;', funcionaría. –

17

respuesta de Ben básicamente golpea el clavo en la cabeza, pero se expanda en que un poco:

El problema aquí es que la gente tiene un natural la expectativa de que un método genérico hará lo mismo que haría el método no genérico equivalente si se proporcionan los tipos en tiempo de compilación. En su caso particular, la gente esperaría que si T es corto, entonces (int)t debería hacer lo correcto: convierte el corto en un int. Y (double)t debe convertir el corto en un doble. Y si T es byte, entonces (int)t debe convertir el byte en un int, y (double)t debe convertir el byte en un doble ... y ahora quizás empiece a ver el problema. El código genérico que tendríamos que generar tendría básicamente que iniciar el compilador de nuevo en tiempo de ejecución y hacer un análisis de tipo completo, y luego generar dinámicamente el código para hacer la conversión como se esperaba.

Eso es potencialmente costoso; añadimos esa característica en C# 4 y si eso es lo que realmente quiere, puede marcar los objetos como de tipo "dinámico" y una versión reducida del compilador se iniciará nuevamente en el tiempo de ejecución y hará la lógica de conversión para usted .

Pero esa cosa cara normalmente no es lo que la gente quiere.

La lógica "as" es ahora menos complicada que la lógica de conversión, ya que no tiene que ocuparse de otras conversiones que no sean boxeo, unboxing y conversiones de referencia. No tiene que ocuparse de conversiones definidas por el usuario, no tiene que ocuparse de representaciones sofisticadas, como conversiones como "byte a double" que transforman las estructuras de datos de un byte en estructuras de datos de ocho bytes, y así sucesivamente.

Es por eso que "as" está permitido en el código genérico pero los moldes no lo son.

Todo lo dicho: es casi seguro que lo estás haciendo mal. Si tiene que hacer una prueba de tipo en el código genérico , su código no es genérico. Este es un olor de código realmente malo.

+3

Para agregar un ejemplo en el que no sea un olor a código, el método 'Enumerable.Count 'es genérico, pero comprueba si TSource es una ICollection para obtener un gran beneficio de rendimiento al llamar. Cuenta en lugar de iterar. Sí, un caso extremo y estoy de acuerdo en que no es un olor a código, pero puede haber buenas razones para ello. –

+2

@MichaelStum: Entiendo tu punto. Sin embargo, sigue siendo algo bastante "genérico" hacer para preguntar * a un objeto * "¿Admite esta interfaz?" El código no genérico también hace ese tipo de cosas. Pero cuando dices "si T pasó a ser exactamente del tipo HashTable, entonces ..." entonces T no se usa en absoluto de una manera genérica. –

+0

Ah, creo que ya veo. Filosóficamente, dices que "me importa que esto implemente un método Count con el comportamiento que espero como lo define ICollection (es decir, no debería hacer cosas funky que no esperaría contando de 1 a X a través de los altavoces)" ? (Solo trato de entender la filosofía del diseño detrás de esto aparte de "Acelera mucho las cosas") –

Cuestiones relacionadas