Genéricos: casting y tipos de valores, ¿por qué es esto ilegal?

¿Por qué es esto un error de tiempo de comstackción?

public TCastTo CastMe(TSource i) { return (TCastTo)i; } 

Error:

tipo de conversión de anotación ‘TSource’ a ‘TCastTo’

¿Y por qué es esto un error de tiempo de ejecución?

 public TCastTo CastMe(TSource i) { return (TCastTo)(object)i; } int a = 4; long b = CastMe(a); // InvalidCastException // this contrived example works int aa = 4; int bb = CastMe(aa); // this also works, the problem is limited to value types string s = "foo"; object o = CastMe(s); 

Busqué SO e Internet para encontrar una respuesta a esto y encontré muchas explicaciones sobre problemas de conversión relacionados con generics similares, pero no puedo encontrar nada en este caso simple en particular.

¿Por qué es esto un error de tiempo de comstackción?

El problema es que cada combinación posible de tipos de valores tiene reglas diferentes para lo que significa un lanzamiento. Lanzar un doble de 64 bits a un int de 16 bits es un código completamente diferente de convertir un decimal a un flotante, y así sucesivamente. El número de posibilidades es enorme. Así que piensa como el comstackdor. ¿Qué código debe generar el comstackdor para su progtwig?

El comstackdor tendría que generar código que inicie el comstackdor de nuevo en tiempo de ejecución, haga un nuevo análisis de los tipos y emita dinámicamente el código apropiado .

Parece que tal vez haya más trabajo y menos rendimiento del que esperaba obtener con los generics, así que simplemente lo prohibimos. Si lo que realmente desea es que el comstackdor se inicie de nuevo y realice un análisis de los tipos, use “dynamic” en C # 4; eso es lo que hace.

¿Y por qué es esto un error de tiempo de ejecución?

Misma razón.

Un int enmarcado solo puede ser desempaquetado a int (o int?), Por la misma razón que arriba; si el CLR intentó hacer todas las conversiones posibles de un tipo de valor encuadrado a cualquier otro tipo de valor posible, entonces esencialmente tiene que ejecutar un comstackdor nuevamente en el tiempo de ejecución . Eso sería inesperadamente lento.

Entonces, ¿por qué no es un error para los tipos de referencia?

Debido a que cada conversión de tipo de referencia es la misma que cualquier otra conversión de tipo de referencia : interroga al objeto para ver si se deriva o es idéntico al tipo deseado. Si no es así, se lanza una excepción (si se realiza un lanzamiento) o se obtiene nulo / falso (si se usan los operadores “como / es”). Las reglas son consistentes para los tipos de referencia de una manera que no son para tipos de valores. Recuerde que los tipos de referencia conocen su propio tipo . Los tipos de valor no; con tipos de valor, la variable que realiza el almacenamiento es lo único que conoce la semántica de tipo que se aplica a esos bits . Los tipos de valores contienen sus valores y no hay información adicional . Los tipos de referencia contienen sus valores más muchos datos adicionales.

Para obtener más información, consulte mi artículo sobre el tema:

http://ericlippert.com/2009/03/03/representation-and-identity/

C # usa una syntax de molde para múltiples operaciones subyacentes diferentes:

  • upcast
  • alicaído
  • boxeo
  • unboxing
  • conversión numérica
  • conversión definida por el usuario

En un contexto genérico, el comstackdor no tiene forma de saber cuál de los dos es correcto y todos generan MSIL diferente, por lo que rescata.

Al escribir return (TCastTo)(object)i; en su lugar, fuerza al comstackdor a hacer un upcast para object , seguido de un TCastTo a TCastTo . El comstackdor generará código, pero si esa no era la forma correcta de convertir los tipos en cuestión, obtendrá un error de tiempo de ejecución.


Muestra de código:

 public static class DefaultConverter { private static Converter cached; static DefaultConverter() { ParameterExpression p = Expression.Parameter(typeof(TSource)); cached = Expression.Lambda(Expression.Convert(p, typeof(TCastTo), p).Compile(); } public static Converter Instance { return cached; } } public static class DefaultConverter { public static TOutput ConvertBen(TInput from) { return DefaultConverter.Instance.Invoke(from); } public static TOutput ConvertEric(dynamic from) { return from; } } 

La manera de Eric es más corta, pero creo que la mía debería ser más rápida.

El error de comstackción se debe a que TSource no se puede convertir implícitamente en TCastTo. Los dos tipos pueden compartir una twig en su árbol de herencia, pero no hay garantía. Si desea llamar solo a los tipos que sí comparten un ancestro, debe modificar la firma CastMe () para usar el tipo ancestro en lugar de los generics.

El ejemplo de error de tiempo de ejecución evita el error en su primer ejemplo al convertir el TSource i en un objeto, algo de lo que todos los objetos en C # derivan. Mientras que el comstackdor no se queja (porque el objeto -> algo que deriva de él, podría ser válido), el comportamiento del lanzamiento a través de la syntax de la variable (Tipo) arrojará si el elenco no es válido. (El mismo problema que el comstackdor impidió que ocurriera en el ejemplo 1).

Otra solución, que hace algo similar a lo que estás buscando …

  public static T2 CastTo(T input, Func convert) { return convert(input); } 

Lo llamarías así.

 int a = 314; long b = CastTo(a, i=>(long)i); 

Espero que esto ayude.