polimorfismo net interfaces herencia hace encapsulamiento ejemplos como clases asp abstractas c# asp.net generics asp.net-mvc-2 nested-generics

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 .



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