如何将通用实例添加到通用对象列表

时间:2018-10-07 17:13:35

标签: c# oop generics inheritance polymorphism

我有一个接口IShape和抽象类Shape。 Shape实现了IShape。形状有2个子元素-圆形和矩形。 我也有通用接口IDrawer,其中T:IShape。我有一个抽象的通用类BaseDrawer:IDrawer,其中T:IShape。

List<IDrawer<IShape>> Drawers { get; set; }

我有一个列表:var drawer = new CircleDrawer(); 尝试创建CircleDrawer的实例-dir *.xls | sort-object -property lastwritetime -descending | select-object -first 1-并将其添加到此列表时,出现错误:无法将CircleDrawer转换为IDrawer。

我需要进行哪些更改才能将circleDrawer实例添加到此列表中?

2 个答案:

答案 0 :(得分:0)

通用协方差和协方差!

因此,您拥有CircleDrawer类型的对象,我们可以将其直接转换为IDrawer<Circle>。让我们看看会发生什么

var list = new List<IDrawer<IShaper>>();
IDrawer<Circle> drawer = new CircleDrawer(); // Completely reasonable cast.

list.Add(drawer); // This results in the error.

好吧,为什么呢?那是因为圆形是形状并非并不暗示圆形抽屉是形状的抽屉。转化

IDrawer<IShape> shapeDrawer = drawer;

是非法的。您实际上想做的是不可能的。圆形抽屉知道如何绘制圆形,而不是形状。假设您尝试的演员表是合法的。我们将抽屉添加到列表中。

list.Add(drawer);

现在在其他地方,我们将其从列表中删除并赋予其形状:

IDrawer<IShape> drawer = list.First();
drawer.Draw(shape);

这是正确的吗?好吧,这取决于shape。如果是

IShape shape = new Circle();

然后,是的,我们在CircleDrawer上画了一个圆圈,一切正常。但是请注意这一行:

IShape shape = new Rectangle();

drawer.Draw(shape);

也是合法的。而且应该给IShape提供IDrawer<IShape>对象似乎是合理的。感觉像它应该工作。但事实并非如此。您刚刚通过给它提供一个矩形代替圆形来调用CircleDrawer.Draw(Circle shape)方法。会发生什么事? CircleDrawer都不希望遇到这种情况。想象一下,如果您被教过如何在整个生命中画圆,突然有人给您一个矩形来绘制:O

因此类型系统不允许列表上的Add。通常,当发生这种情况时,您可以通过标记通用类型协变或逆变来修复它。但是在这种情况下,您想要做的事情实际上是不可能的并且是荒谬的-一组您不知道的确切类型的抽屉对您没有用。您不知道他们会画些什么,因此每次进行Draw通话时,您都在玩俄罗斯轮盘赌,并希望刚通过的矩形传递到RectangleDrawer而不是CircleDrawer

您唯一可以做的就是反过来做事-假设您有一个RectangleDrawer和一个SquareDrawer

class Rectangle : IShape {}

class Square : Rectangle {}

class RectangleDrawer : IDrawer<Rectangle> {}

class SquareDrawer : IDrawer<Square> {}

那么方形抽屉的集合就很好了,您可能想要做类似的事情

var list = new List<SquareDrawer>();

var squareDrawer = new SquareDrawer();
var rectangleDrawer = new RectangleDrawer();

list.Add(squareDrawer);
list.Add(rectangleDrawer);

然后您可以使用该列表在其中绘制抽屉。这是有道理的,因为成为RectangleDrawer意味着您可以绘制任何矩形,包括正方形。

但是以上几行将无法编译-您必须标记IDrawer的反变。

interface IDrawer<in T> where T : IShape
{
    void Draw(T shape);
}

这告诉编译器IDrawer<T>如果U也可以绘制U : T。它还不允许将T指定为任何成员方法的返回类型。

有关协方差和协方差的更多信息,请参见in the MSDN docs

答案 1 :(得分:0)

让我们考虑一下。

您是说CircleDrawerIDrawer<IShape>的一种类型,因为CircleIShape的一种类型。

但是IDrawer<IShape>可以吸引IShape-任何IShape。现在CircleDrawer可以画一个圆形,但是不能画其他形状,所以它不是IDrawer<IShape>

理论上,IDrawer<IShape>可以是IDrawer<Circle>的实例,因为它可以绘制任何形状,包括圆形。要正式指定,您必须使用协方差/对比度:https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance

对于您而言,恐怕您必须创建一个List<IDrawer<Circle>>

要显示在允许您将CircleDrawer视为IDrawer<IShape>的情况下可能出问题的地方,请考虑以下代码:

IDrawer<IShape> drawer = new CircleDrawer();
drawer.Draw(new Rectangle()); //Throws exception - a circle drawer can't draw a rectangle