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).