2010-08-20 17 views
16

Sé que esto es antiguo, pero aún no soy muy bueno para comprender esos problemas. ¿Alguien puede decirme por qué lo siguiente no funciona (arroja una excepción runtime sobre el casting)?Genéricos y casting: no se puede convertir la clase hereditaria a la clase base

public abstract class EntityBase { } 
public class MyEntity : EntityBase { } 

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

Y ahora la línea de colada:

MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever 
RepositoryBase<EntityBase> baseRepo = (RepositoryBase<EntityBase>)myEntityRepo; 

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

+1

Gracias a todos por las respuestas. Para abreviar, resolví este problema con una interfaz base (RepositoryBase : IRepository). Resulta que solo necesito ejecutar las funciones en la instancia que obtengo y dejar que la clase maneje otras cosas. – Jefim

+0

Consulte las [Preguntas frecuentes sobre covarianza y contradicción de C#] (http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx) –

Respuesta

24

RepositoryBase<EntityBase> es no una clase base de MyEntityRepository. Está buscando varianza genérica que existe en C# en cierta medida, pero no se aplica aquí.

Suponga que su clase RepositoryBase<T> tenía un método como este:

void Add(T entity) { ... } 

Consideremos ahora:

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

Ahora hemos añadido un tipo diferente de entidad a una MyEntityRepository ... y que puede estar bien.

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 contravariación 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 delegados genéricos, no clases, y solo con tipos de referencia. Consulte MSDN para obtener más información, lea <enchufe> lea C# in Depth, 2nd edition, capítulo 13 </enchufe > o Eric Lippert blog series sobre el tema. Además, di una charla de una hora sobre esto en NDC en julio de 2010 - el video está disponible here; solo busque "varianza".

+0

Gracias Jon, ambos por la explicación y el enlace a material muy interesante. Definitivamente consideraré leer el libro y mirar el video. – Jefim

6

Esto requiere covarianza o contravarianza, cuyo soporte está 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<out T> where T : EntityBase { //or "in" depending on the items. 
    } 
    public abstract class RepositoryBase<T> : IRepository<T> where T : EntityBase { 
    } 
    public class MyEntityRepository : RepositoryBase<MyEntity> { 
    } 

    ... 

    IRepository<EntityBase> baseRepo = (IRepository<EntityBase>)myEntityRepo; 
11

Cada vez que alguien hace esta pregunta, trato de tomar su ejemplo y traducirlo a algo con clases más conocidas que es obviamente ilegal (esto es lo que Jon Skeet has done in his answer, pero lo estoy tomando un paso más allá realizando esta traducción).

Vamos a sustituir MyEntityRepository con MyStringList, así:

class MyStringList : List<string> { } 

Ahora, que parecen querer MyEntityRepository para ser moldeable a RepositoryBase<EntityBase>, por la razón de que esto debería ser posible, ya que se deriva de MyEntityEntityBase.

Pero string deriva de object, ¿o sí? Entonces, con esta lógica, deberíamos poder lanzar un MyStringList a un List<object>.

Vamos a ver lo que puede suceder si permitimos que ...

var strings = new MyStringList(); 
strings.Add("Hello"); 
strings.Add("Goodbye"); 

var objects = (List<object>)strings; 
objects.Add(new Random()); 

foreach (string s in strings) 
{ 
    Console.WriteLine("Length of string: {0}", s.Length); 
} 

Uh-oh. Repentinamente estamos enumerando sobre un List<string> y encontramos un objeto Random. Eso no es bueno.

Afortunadamente, esto hace que el problema sea un poco más fácil de entender.

+4

Empecé a ir un paso más allá en los ejemplos: solía usar cuerdas y objetos, pero a sugerencia de Eric Lippert comencé a usar objetos del mundo real ... Fruit/Apple/Banana funciona bien en términos de "usted puede" t agrega una manzana a un manojo de plátanos ". –

Cuestiones relacionadas