2009-11-05 14 views
16

Duplicar posible:
Puzzling Enumerable.Cast InvalidCastExceptionEnumerable.Cast <T> método de extensión falla al lanzar desde int a long, ¿por qué?

Hola,

acabo de notar algo muy extraño con el método de extensión Enumerable.Cast<T> ... Parece que no puede lanzar desde int a long , a pesar de que este elenco es perfectamente legal.

El siguiente código falla con un InvalidCastException:

 foreach (var item in Enumerable.Range(0,10).Cast<long>()) 
     { 
      Console.WriteLine(item); 
     } 

Pero este código, que se presume equivalente, no trabajo:

 foreach (var item in Enumerable.Range(0,10).Select(i => (long)i)) 
     { 
      Console.WriteLine(item); 
     } 

Puede alguien explicar ese comportamiento? Miré el código del método moldeada con reflector, pero no puedo interpretar Reflector bloques de iterador, así que es bastante difícil de entender ...

+2

duplicado posible: http://stackoverflow.com/questions/445471 –

+0

sí, usted' Re correcto ... Lo extrañé en mi búsqueda –

+2

Me sorprende que ninguna de las respuestas realmente responda la pregunta * por qué *. La respuesta es porque la conversión de int a long ** no es un elenco **. Es una * conversión *. Es desafortunado que C# use la misma sintaxis para ambos, ya que solo confunde a las personas (aparentemente). Tampoco puede usar '.Cast ()' para invocar a un operador de conversión explícita personalizado porque tampoco es un elenco. – Timwi

Respuesta

16

la línea relevante en Cast:

this.<>2__current = (TResult)this.<obj>5__ab; 

Nos puede imitar esto usando el siguiente código:

int foo = 1; 
long bar = Cast<long>(foo); //oh noes! 

T Cast<T>(object input) 
{ 
    return (T)input; 
} 

Which also fails. La clave aquí es que en el momento del lanzamiento, es un objeto. No es una int. Esto falla porque solo podemos deshacer la casilla de un objeto al tipo exacto que queremos. Estamos yendo del objeto, que podría ser una caja larga, pero no lo es. Es un int en caja Eric Lippert discussed this on his blog:

hemos decidido que unboxing solo puede desagregar al tipo exacto. Si desea llamar al método lento que hace todo eso, está disponible - siempre puede llamar a Convert ...

En su código que funciona, no se trata de un int enmarcado (un objeto) , tienes un int.

+11

De hecho. Tenga en cuenta que la versión anterior del operador de secuencia Cast * se equivocó * y accidentalmente permitió conversiones como esta para * tener éxito * cuando por diseño deberían fallar. (Y también fue muy, muy lento.) Esa es la peor situación de error posible, donde para hacer que el código se comporte correctamente, tiene que potencialmente romper a los clientes existentes. Tomamos el golpe; el código ahora es correcto y rápido, no excesivamente indulgente y lento, pero nos sentimos muy mal al respecto. Me siento especialmente mal, ya que revisé el código de la implementación incorrecta y no detecté los problemas antes del lanzamiento. ¡Lo siento! –

+0

Por supuesto, me olvidé del boxeo ... explicación perfecta, ¡gracias! –

+8

¡Woohoo! Dije algo y Eric Lippert dijo "de hecho". Acabo de hacer mi mes: D –

8

A diferencia de la mayoría de los otros métodos de extensión LINQ, Cast amplía la interfaz no genérica IEnumerable, en lugar de IEnumerable<T>.

Esto significa que los int valores generados por la llamada Range están enmarcadas por empadronador subyacente del Cast de llamada, que a su vez intenta echarlos a long y fracasa, porque los valores sólo pueden ser sacó de la caja con el mismo tipo exacto.

Puede imitar el mismo comportamiento excepción en el segundo bucle de boxeo de forma explícita las int valores:

foreach (var item in Enumerable.Range(0, 10).Select(i => (long)(object)i)) 
{ 
    Console.WriteLine(item); 
} 
+0

¡Exactamente! pero Rex M estaba primero, lo siento ... ¡Gracias de todos modos! –

+0

Esta es la explicación más correcta de por qué. +1 –

1

El problema es que MoveNext de CastIterator cajas el valor actual y los intentos de desempacar al tipo de destino (donde el valor en caja no es del tipo correcto) por lo que el desempaquetado falla durante la verificación de tipo.

Referencia Info:

http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.unbox_any.aspx

L_003c: ldarg.0 
    L_003d: ldarg.0 
    L_003e: ldfld class [mscorlib]System.Collections.IEnumerator System.Linq.Enumerable/<CastIterator>d__aa<!TResult>::<>7__wrapac 
    L_0043: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current() 
    L_0048: stfld object System.Linq.Enumerable/<CastIterator>d__aa<!TResult>::<obj>5__ab 
    L_004d: ldarg.0 
    L_004e: ldarg.0 
    L_004f: ldfld object System.Linq.Enumerable/<CastIterator>d__aa<!TResult>::<obj>5__ab 
    L_0054: unbox.any !TResult 

La solución consiste en utilizar a Seleccione()

Cuestiones relacionadas