如何将泛型实例添加到泛型对象列表中



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

public interface IShape
{
double M1();
double M2();
}
public abstract class Shape : IShape
{
public abstract double M1();
public abstract double M2();
}
public class Circle : Shape
{
}
public class Rectangle: Shape
{
}
public interface IDrawer<T> where T:IShape
{
void Draw(T shape);
}  
public abstract class BaseDrawer<T> : IDrawer<T> where T : IShape
{
public abstract void Draw(T shape);
}
public class CircleDrawer : BaseDrawer<Circle>
{
public override void Draw(Circle circle)
{
}
}
public class RectangleDrawer : BaseDrawer<Rectangle>
{
public override void Draw(Rectangle rectangle)
{
}
}

我有一个清单:List<IDrawer<IShape>> Drawers { get; set; }当我尝试创建CircleDrawer的实例(var drawer = new CircleDrawer();(并将其添加到此列表中时,我收到一个错误:无法将CircleDrawer转换为IDrawer。

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

泛型协方差和逆变 我们开始了!

所以你有CircleDrawer类型的东西,我们可以直接转换为IDrawer<Circle>.让我们看看会发生什么

var list = new List<IDrawer<IShape>>();
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);

也是合法的。应该是,给IDrawer<IShape>一个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);
}

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

有关协变和逆变的更多信息,请参阅 MSDN 文档。

让我们考虑一下。

你是说CircleDrawer是一种IDrawer<IShape>,因为Circle是一种IShape

但是IDrawer<IShape>是可以引起IShape的东西 - 任何IShape.现在CircleDrawer可以画一个圆,但没有其他形状,所以它不是IDrawer<IShape>

然而,理论上IDrawer<IShape>可以是IDrawer<Circle>的实例,因为它可以绘制任何形状,包括一个圆。要正式指定,您必须使用协方差/逆变:https://learn.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

最新更新