Приведение типа 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(), библиотека должны cast и проверьте, является ли 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.
      }
   }
}

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

3 ответов


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

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

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

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

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

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

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

а затем приведите к IParent в своей функции.


что-то вроде следующих строк?

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