net - polimorfismo e interfaces c#
Por favor, ayúdame a entender el polimorfismo al usar genéricos en c# (3)
Tengo problemas para entender cómo funciona el polimorfismo cuando utilizo medicamentos genéricos. Como ejemplo, he definido el siguiente programa:
public interface IMyInterface
{
void MyMethod();
}
public class MyClass : IMyInterface
{
public void MyMethod()
{
}
}
public class MyContainer<T> where T : IMyInterface
{
public IList<T> Contents;
}
Entonces puedo hacer esto, que funciona bien:
MyContainer<MyClass> container = new MyContainer<MyClass>();
container.Contents.Add(new MyClass());
Tengo muchas clases que implementan MyInterface. Me gustaría escribir un método que pueda aceptar todos los objetos MyContainer:
public void CallAllMethodsInContainer(MyContainer<IMyInterface> container)
{
foreach (IMyInterface myClass in container.Contents)
{
myClass.MyMethod();
}
}
Ahora, me gustaría llamar a este método.
MyContainer<MyClass> container = new MyContainer<MyClass>();
container.Contents.Add(new MyClass());
this.CallAllMethodsInContainer(container);
Eso no funcionó. Seguramente, como MyClass implementa IMyInterface, ¿debería poder lanzarlo?
MyContainer<IMyInterface> newContainer = (MyContainer<IMyInterface>)container;
Eso tampoco funcionó. Definitivamente puedo lanzar MyClass normal a IMyInterface:
MyClass newClass = new MyClass();
IMyInterface myInterface = (IMyInterface)newClass;
Entonces, al menos no lo he entendido completamente. No estoy seguro de cómo debo escribir un método que acepte una colección genérica de clases que se ajusten a la misma interfaz.
Tengo un plan para hackear completamente este problema si es necesario, pero realmente preferiría hacerlo correctamente.
Gracias de antemano.
Nota: En todos los casos, deberá inicializar el campo de Contents a un objeto concreto que implemente IList<?>
Cuando mantiene la restricción genérica, puede hacer:
public IList<T> Contents = new List<T>();
Cuando no lo haces, puedes hacer:
public IList<MyInterface> Contents = new List<MyInterface>();
Método 1:
Cambiar el método a:
public void CallAllMethodsInContainer<T>(MyContainer<T> container) where T : IMyInterface
{
foreach (T myClass in container.Contents)
{
myClass.MyMethod();
}
}
y el fragmento para:
MyContainer<MyClass> container = new MyContainer<MyClass>();
container.Contents.Add(new MyClass());
this.CallAllMethodsInContainer(container);
Método 2:
Alternativamente, mueva el método CallAllMethodsInContainer a la MyContainer<T> la siguiente manera:
public void CallAllMyMethodsInContents()
{
foreach (T myClass in Contents)
{
myClass.MyMethod();
}
}
y cambie el fragmento a:
MyContainer<MyClass> container = new MyContainer<MyClass>();
container.Contents.Add(new MyClass());
container.CallAllMyMethodsInContents();
Método 3:
EDITAR: Otra alternativa más es eliminar la restricción genérica de la clase MyContainer esta manera:
public class MyContainer
{
public IList<MyInterface> Contents;
}
y para cambiar la firma del método a
public void CallAllMethodsInContainer(MyContainer container)
Entonces, el fragmento debería funcionar como:
MyContainer container = new MyContainer();
container.Contents.Add(new MyClass());
this.CallAllMethodsInContainer(container);
Tenga en cuenta que con esta alternativa, la lista de contenidos del contenedor aceptará cualquier combinación de objetos que implemente MyInterface .
este es un problema de covarianza
http://msdn.microsoft.com/en-us/library/dd799517.aspx
no puede convertir MyContainer<MyClass> en MyContainer<IMyInterface> porque entonces podría hacer cosas como Contents.Add(new AnotherClassThatImplementsIMyInterface())
Wow, esta pregunta ha estado surgiendo mucho últimamente.
Respuesta corta: No, esto no es posible. Esto es lo que es posible:
public void CallAllMethodsInContainer<T>(MyContainer<T> container) where T : IMyInterface
{
foreach (IMyInterface myClass in container.Contents)
{
myClass.MyMethod();
}
}
Y he aquí por qué lo que probaste no es posible (tomado de esta respuesta reciente mía ):
Considere el tipo List<T> . Supongamos que tiene una List<string> y una List<object> . string deriva del objeto, pero no sigue que List<string> deriva de List<object> ; si lo hiciera, entonces podrías tener un código como este:
var strings = new List<string>();
// If this cast were possible...
var objects = (List<object>)strings;
// ...crap! then you could add a DateTime to a List<string>!
objects.Add(new DateTime(2010, 8, 23));23));
El código anterior ilustra lo que significa ser (y no ser) un tipo covariante . Tenga en cuenta que convertir un tipo T<D> a otro tipo T<B> donde D deriva de B es posible (en .NET 4.0) si T es covariante ; un tipo genérico es covariante si su argumento de tipo genérico solo aparece en forma de salida, es decir, propiedades de solo lectura y valores de retorno de función.
Piénselo de esta manera: si algún tipo T<B> siempre suministra un B , entonces uno que siempre suministre un D ( T<D> ) podrá operar como un T<B> ya que todos los D s son B s.
Incidentalmente, un tipo es contravariante si su parámetro de tipo genérico solo aparece en forma de entrada, es decir, parámetros del método. Si un tipo T<B> es contravariante, entonces puede convertirse en T<D> , por extraño que parezca.
Piénselo de esta manera: si algún tipo T<B> siempre requiere una B , entonces puede intervenir en una que siempre requiere una D ya que, nuevamente, todas las D s son B s.
Su clase MyContainer no es covariante ni contravariante porque su parámetro de tipo aparece en ambos contextos: como entrada (a través de Contents.Add ) y como salida (a través de la propiedad Contents misma).