我有一个接口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实例添加到此列表中?
答案 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)
让我们考虑一下。
您是说CircleDrawer
是IDrawer<IShape>
的一种类型,因为Circle
是IShape
的一种类型。
但是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