2012-04-13 27 views
7

Estoy escribiendo una biblioteca que representa un grupo de objetos secundarios para revisar. El objeto secundario es abstracto, y está destinado a que los usuarios de esta biblioteca obtengan su propio hijo de esta clase abstracta.Fundición de tipo C# en una interfaz genérica

public abstract class Child : IRenderable {} 

public interface IParent<T> where T : Child 
{ 
    IEnumerable<T> Children { get; } 
} 

La complicación es que no tengo una lista de iParent a trabajar, en cambio, tengo un montón de IRenderables. Se espera que el usuario de la biblioteca para escribir algo como esto:

public class Car : IRenderable { } 
public class Cow : IRenderable, IParent<Calf> { } 
public class Calf : Child { } 

// note this is just an example to get the idea 
public static class App 
{ 
    public static void main() 
    { 
     MyLibraryNameSpace.App app = new MyLibraryNameSpace.App(); 
     app.AddRenderable(new Car()); // app holds a list of IRenderables 
     app.AddRenderable(new Cow()); 
     app.Draw(); // app draws the IRenderables 
    } 
} 

En Draw(), el elenco de la biblioteca debe y comprobar si el IRenderable es también un iParent. Sin embargo, dado que no sé nada sobre el ternero, no sé a qué echarle la vaca.

// In Draw() 
foreach(var renderable in Renderables) 
{ 
    if((parent = renderable as IParent<???>) != null) // what to do? 
    { 
     foreach(var child in parent.Children) 
     { 
      // do something to child here. 
     } 
    } 
} 

¿Cómo puedo solucionar este problema? ¿Tiene esto algo que ver con los genéricos de covarianza o con lo que nunca (no estoy familiarizado con el concepto de covarianza)?

Respuesta

9

Desde IParent<T> sólo vuelve artículos de tipo T, usted podría hacerlo covariante utilizando el out modifier:

public interface IParent<out T> where T : Child 
{ 
    IEnumerable<T> Children { get; } 
} 

Esto haría IParent<anything> convertibles en IParent<Child>:

IParent<Child> parent = renderable as IParent<Child>; // works for Cow 

Nota esa covarianza solo funciona siempre y cuando Usted solo está devolviendo objetos de tipo T (simplemente hablando). Por ejemplo, tan pronto como se agrega un método AddChild(T) a su interfaz IParent, covarianza debe romper (= el compilador se quejará), ya que, de lo contrario, el siguiente código de tipo inseguro podría escribirse:

IParent<Child> parent = renderable as IParent<Child>; 
parent.AddChild(new Kitten()); // can't work if parent is really a Cow. 
+0

'IParent ' no es legal (no satisfará la condición 'T: Child'), y realmente no lo convierte en * superclase * - sino que permite que el compilador y el tiempo de ejecución utilicen la varianza. –

+0

@MarcGravell: Gracias, corregido. Pasó por alto la restricción. – Heinzi

+1

Apreciar la explicación adicional en las ediciones. Realmente ayudó mucho. Gracias. – Jake

1

Se podría implementar la interfaz intermedia no genérico iParent:

public interface IParent 
{ 
    IEnumerable<Child> Children { get; } 
} 

public interface IParent<T> : IParent 
    where T: Child 
{ 
    IEnumerable<T> Children { get; } 
} 

Y luego echados a iParent en su función.

+0

Antes de C# 4.0 este era nuestro límite, pero a partir de C# 4.0 en adelante, el enfoque de varianza descrito en la respuesta de Heinzi es generalmente preferible. –

+0

Es útil saber que esto funcionará en 3.0. – Jake

1

Algo a lo largo las siguientes líneas?

static void draw(List<IRenderable> renderables) 
{ 
    foreach (IRenderable render in renderables) 
    { 
     if (render is IParent<Child>) 
     { 
      foreach (Child c in ((IParent<Child>)render).Children) 
      { 
       //do something with C? 
      } 
     } 
    } 
} 
Cuestiones relacionadas