c# - que - Genéricos y casting: no se puede convertir la clase heredada en la clase base
que es override en programacion (3)
Sé que esto es viejo, pero todavía no soy muy bueno para entender esos problemas. ¿Alguien puede decirme por qué lo siguiente no funciona (lanza una excepción de runtime
sobre el lanzamiento)?
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 casting:
MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever
RepositoryBase<EntityBase> baseRepo = (RepositoryBase<EntityBase>)myEntityRepo;
Entonces, ¿alguien puede explicar cómo esto no es válido? Y, no tiene ganas de explicarlo, ¿hay una línea de código que pueda usar para hacer este lanzamiento?
Cada vez que alguien hace esta pregunta, trato de tomar su ejemplo y traducirlo a algo usando clases más conocidas que obviamente es ilegal (esto es lo que Jon Skeet ha hecho en su respuesta ; pero estoy avanzando un paso más al interpretar esta traducción).
Reemplazemos MyEntityRepository
con MyStringList
, de esta manera:
class MyStringList : List<string> { }
Ahora, parece que quiere que MyEntityRepository
pueda convertirse en RepositoryBase<EntityBase>
, el razonamiento es que esto debería ser posible 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 lista MyStringList
en una List<object>
.
A ver qué puede pasar 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. De repente, estamos enumerando sobre una List<string>
y encontramos un objeto Random
. Eso no es bueno.
Esperemos que esto haga que el problema sea un poco más fácil de entender.
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 depósito de IR 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;
RepositoryBase<EntityBase>
no es una clase base de MyEntityRepository
. Está buscando una varianza genérica que existe en C # hasta cierto punto, pero no se aplicaría aquí.
Supongamos que su clase RepositoryBase<T>
tiene un método como este:
void Add(T entity) { ... }
Ahora considera:
MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever
RepositoryBase<EntityBase> baseRepo = (RepositoryBase<EntityBase>)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" el API (por ejemplo, una comparación general que puede comparar dos formas por área puede considerarse como una comparación de cuadrados).
En C # 4, esto está disponible para interfaces genéricas y delegados genéricos, no para clases, y solo con tipos de referencia. Consulte MSDN para obtener más información, lea <plug>, lea C # en profundidad, 2ª edición , capítulo 13 </plug> 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 here .