Generics and casting: no se puede convertir la clase heredada en clase base

Sé que esto es viejo, pero aún no soy muy bueno para entender esos problemas. ¿Alguien puede decirme por qué lo siguiente no funciona (arroja una excepción de runtime sobre el casting)?

 public abstract class EntityBase { } public class MyEntity : EntityBase { } public abstract class RepositoryBase where T : EntityBase { } public class MyEntityRepository : RepositoryBase { } 

Y ahora la línea de lanzamiento:

 MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever RepositoryBase baseRepo = (RepositoryBase)myEntityRepo; 

Entonces, ¿alguien puede explicar cómo es esto inválido? Y, no estoy de humor para explicar, ¿hay alguna línea de código que pueda usar para hacer este reparto?

RepositoryBase no es una clase base de MyEntityRepository . Está buscando la varianza genérica que existe en C # hasta cierto punto, pero no se aplicaría aquí.

Supongamos que su clase RepositoryBase tiene un método como este:

 void Add(T entity) { ... } 

Ahora considera:

 MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever RepositoryBase baseRepo = (RepositoryBase)myEntityRepo; baseRepo.Add(new OtherEntity(...)); 

Ahora ha agregado un tipo diferente de entidad a un MyEntityRepository … y eso no puede ser correcto.

Básicamente, la varianza genérica solo es segura en ciertas situaciones. En particular, la covarianza genérica (que es lo que está describiendo aquí) solo es segura cuando solo obtiene valores “fuera” de la API; la contravarianza genérica (que funciona al revés) solo es segura cuando solo se ponen valores “en” la API (por ejemplo, una comparación general que puede comparar dos formas por área se puede considerar como una comparación de cuadrados).

En C # 4 esto está disponible para interfaces genéricas y delegates generics, no para clases, y solo con tipos de referencia. Consulte MSDN para obtener más información, lea lea C # en profundidad, segunda edición , capítulo 13 o la serie de blogs de Eric Lippert sobre el tema. Además, di una charla de una hora sobre esto en NDC en julio de 2010; el video está disponible aquí .

Cada vez que alguien hace esta pregunta, trato de tomar su ejemplo y traducirlo a algo utilizando clases más conocidas que obviamente son ilegales (esto es lo que Jon Skeet ha hecho en su respuesta , pero voy un paso más allá al realizar esta traducción).

Reemplace MyEntityRepository con MyStringList , así:

 class MyStringList : List { } 

Ahora parece que desea que MyEntityRepository pueda convertir en RepositoryBase , ya que MyEntity deriva de EntityBase .

Pero la string deriva del object , ¿no es así? Entonces, con esta lógica, deberíamos poder convertir una MyStringList de MyStringList en una List .

Veamos qué puede pasar si permitimos que …

 var strings = new MyStringList(); strings.Add("Hello"); strings.Add("Goodbye"); var objects = (List)strings; objects.Add(new Random()); foreach (string s in strings) { Console.WriteLine("Length of string: {0}", s.Length); } 

UH oh. De repente, estamos enumerando una List y encontramos un objeto Random . Eso no es bueno.

Con suerte, esto hace que el tema sea un poco más fácil de entender.

Esto requiere covarianza o contravarianza, cuyo soporte es limitado en .Net, y no se puede usar en clases abstractas. Sin embargo, puede usar la varianza en las interfaces, por lo que una posible solución a su problema es crear un IRepository que use en lugar de la clase abstracta.

  public interface IRepository where T : EntityBase { //or "in" depending on the items. } public abstract class RepositoryBase : IRepository where T : EntityBase { } public class MyEntityRepository : RepositoryBase { } ... IRepository baseRepo = (IRepository)myEntityRepo;