2012-06-26 27 views
11

Tengo un método de fábrica simple que proporciona una instancia de implementación concreta basada en un parámetro de tipo genérico proporcionado. Si las clases concretas heredan de una clase base abstracta común con un parámetro de tipo, no puedo convertirlas. El compilador me dice Error 2 Cannot convert type 'Car' to 'VehicleBase<T>'. Funciona bien si sustituyo la clase abstracta por una interfaz con el mismo parámetro de tipo, o si elimino el parámetro de tipo genérico de la clase abstracta.No se puede convertir el tipo derivado a la clase abstracta base con el parámetro de tipo

interface IWheel 
{ 
} 

class CarWheel : IWheel 
{ 
} 

abstract class VehicleBase<T> 
{ 
} 

class Car : VehicleBase<CarWheel> 
{ 
} 

class VehicleFactory 
{ 
    public static VehicleBase<T> GetNew<T>() 
    { 
     if (typeof(T) == typeof(CarWheel)) 
     { 
      return (VehicleBase<T>)new Car(); 
     } 
     else 
     { 
      throw new NotSupportedException(); 
     } 
    } 
} 

Esta falla al compilar en (VehicleBase<T>)new Car(). ¿Es esto un defecto del compilador, o podría ser una decisión de diseño deliberada tratar las clases abstractas y las interfaces con los parámetros de tipo de forma diferente?

Como solución alternativa siempre puedo hacer que la clase abstracta implemente una interfaz y usar esto como el valor de retorno para mi método de fábrica, pero aún me gustaría saber por qué ocurre este comportamiento.

Respuesta

5

Esto no es un defecto del compilador ni una decisión deliberada. Los parámetros de tipo en las clases genéricas son neither covariant nor contravariant, es decir, no existe una relación de herencia entre las especializaciones de la misma clase genérica. De los documentos:

En .NET Framework versión 4, los parámetros de tipo de variante están restringidos a la interfaz genérica y tipos de delegados genéricos.

Lo que significa que el siguiente código compilará, ya que utiliza una interfaz en lugar de una clase abstracta:

interface IWheel 
{ 
} 

class CarWheel : IWheel 
{ 
} 

interface IVehicleBase<T> 
{ 
} 

class Car : IVehicleBase<CarWheel> 
{ 
} 

class VehicleFactory 
{ 
    public static IVehicleBase<T> GetNew<T>() 
    { 
     if (typeof(T) == typeof(CarWheel)) 
     { 
      return (IVehicleBase<T>)new Car(); 
     } 
     else 
     { 
      throw new NotSupportedException(); 
     } 
    } 
} 

Comprobar "Covariance and Contravariance in Generics" para obtener más información y ejemplos.

También hay un Covariance and Contravariance FAQ en el C# FAQ blog con más información, y un 11-part series! sobre el tema por Eric Lippert

+0

Esta es la solución que había utilizado. Sin embargo, gracias por recordarme sobre la varianza en las interfaces/delegados, tiene sentido y responde mi pregunta. – dahvyd

+0

Gracias por recordarme. Es demasiado fácil olvidarlo cuando solo tienes que sacar un código de la puerta –

9

Eso no es demostrable , porque el código genérico necesita trabajar (con el mismo IL) para todos los posible T, y no hay nada que decir que Car : VehicleBase<float>, por ejemplo. El compilador no analiza en exceso el hecho de que el control if siembra que T es CarWheel - el verificador estático trata cada declaración por separado, no intenta comprender la causa y el efecto de las condiciones.

para forzarlo, fundido a object en el medio:

return (VehicleBase<T>)(object)new Car(); 

Sin embargo! Su enfoque no es realmente "genérico" como tal.

+0

suena razonable, pero ¿por qué la diferencia si uso una interfaz? Esto es lo que no entiendo. – dahvyd

+0

No es solo que esto no sea comprobable. Solo las interfaces genéricas pueden tener tipos de variantes. Este código no funcionaría aunque haya especificado las restricciones de tipo adecuadas –

3

Esto parece funcionar:

return new Car() as VehicleBase<T>; 

Mi suposición por qué es de esa manera:
Como ejemplos de tipo genérico de VehicleBase<T> no están relacionados, no puede ser probado que echarlos podría funcionar:

enter image description here

Si T es de tipo Blah, el elenco no funcionaría. No puede volver al objeto y luego tomar la otra rama (no hay herencia múltiple en C# después de todo).

Al devolverlo a object antes, de nuevo está abriendo la posibilidad de que el molde funcione, porque aún podría haber una ruta hasta VehicleBase<CarWheel>. Una interfaz, por supuesto, puede aparecer en cualquier parte en este árbol debajo de object, por lo que debería funcionar también.

Cuestiones relacionadas