2012-04-13 4 views
7

Я пишу библиотеку, которая отображает кучу ребенка объектов на экран. Детский объект является абстрактным и предназначен для пользователей этой библиотеки, чтобы вывести свой собственный ребенок из этого абстрактного класса.Листинг типа C# на общий интерфейс

public abstract class Child : IRenderable {} 

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

Сложность заключается в том, что у меня нет списка IParent для работы, вместо этого, у меня есть куча IRenderables. Пользователь библиотеки, как ожидается, чтобы написать что-то вроде этого:

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 
    } 
} 

В Draw(), библиотека должна литая и проверить, является ли также IRenderable IParent. Однако, поскольку я не знаю о теленке, я не знаю, что бросить корову.

// 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. 
     } 
    } 
} 

Как я могу решить эту проблему? Это что-то общего с ковариантными дженериками или тем, что когда-либо (я не знаком с концепцией ковариации)?

ответ

9

С IParent<T> только возвращает элементов типа T, вы могли бы сделать его ковариантны помощью out modifier:

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

Это сделало бы IParent<anything> конвертируемые в IParent<Child>:

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

Примечание что ковариация работает только до тех пор, пока u только возвращение объекты типа T (просто говоря). Например, как только вы добавляете AddChild(T) метод к интерфейсу IParent, ковариационная должен разорвать (= компилятор будет жаловаться), так как, в противном случае, следующий тип-небезопасный код может быть написан:

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

'IParent ' не является законным (он не будет удовлетворять условию «T: Child»), и он на самом деле не делает его * суперклассом * - скорее, это позволяет компилятору и среде выполнения использовать дисперсию. –

+0

@MarcGravell: Спасибо, исправлено. Сменила ограничение. – Heinzi

+1

Оцените дополнительное объяснение в редактировании. Очень помог. Благодарю. – Jake

1

Вы могли бы реализовать промежуточный необщего интерфейс IParent:

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

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

А затем отливали в IParent в вашей функции.

+0

До C# 4.0 это был наш предел, но с C# 4.0 и выше подход дисперсии, изложенный в ответе Хайнци, обычно предпочтительнее. –

+0

Полезно знать, что это будет работать в версии 3.0. – Jake

1

Что-то вдоль следующие строки?

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? 
      } 
     } 
    } 
} 
Смежные вопросы