将C#类型转换为通用接口

7

我正在编写一个库,用于将一堆子对象渲染到屏幕上。这个子对象是抽象的,旨在让该库的用户从这个抽象类派生他们自己的子类。

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。然而,由于我不了解Calf,我不知道如何将Cow进行类型转换。

// 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个回答

9

由于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的对象时,协变才有效(简单来说)。例如,一旦您向IParent接口添加一个AddChild(T)方法,协变必须被打破(=编译器将会抱怨),否则可能会编写以下不安全的代码:
IParent<Child> parent = renderable as IParent<Child>;
parent.AddChild(new Kitten()); // can't work if parent is really a Cow.

IParent<object> 不合法(它不满足 T : Child 的条件),而且它并不是真正的 超类 - 相反,它允许编译器和运行时利用协变性。 - Marc Gravell
@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。


在C# 4.0之前,这是我们的极限,但从C# 4.0开始,Heinzi答案中概述的方差方法通常更可取。 - Marc Gravell

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接