c# - 如何将通用实例添加到通用对象列表中
问题描述
我有一个接口 IShape 和抽象类 Shape。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);
也将是合法的。它应该是,给一个IShape
对象IDrawer<IShape>
似乎是合理的。感觉应该可以。但事实并非如此。你只是CircleDrawer.Draw(Circle shape)
通过给它一个矩形来代替圆来调用一个方法。会发生什么?这不是任何CircleDrawer
人都愿意遇到的情况。想象一下,如果你被教导如何在你的一生中画圆,突然有人给你一个矩形来画:O
所以类型系统不允许Add
列表中的。通常,当这种情况发生时,您可以通过标记泛型类型协变或逆变来修复它。但是在这种情况下,您想要做的事情实际上是不可能的和荒谬的 - 您不知道确切类型的抽屉的集合对您毫无用处。你不知道他们能画什么,所以每次Draw
打电话你都在玩俄罗斯轮盘赌,希望你刚通过的矩形变成 aRectangleDrawer
而不是 a CircleDrawer
。
你唯一能做的就是反其道而行之——假设你有 aRectangleDrawer
和 aSquareDrawer
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);
}
这告诉编译器 aIDrawer<T>
也可以绘制U
if U : T
。它还不允许指定T
任何成员方法的返回类型。
更多关于协变和逆变的信息可以在 MSDN 文档中找到。
推荐阅读
- sql - Sql 语句获取每月未下订单的客户(同时显示月份)
- api - 微服务 API URL 模式
- python - 用python中的匹配替换所有出现
- android - 使用约束布局等间距的四个图像
- testing - 如何同时测试多个用户?
- python - 带有可能看不见的数据的标签编码
- spring - 在没有 MockMvc 的情况下为 RestAPI 编写 Spock 测试
- python - 如何在 Python 中重用一个大的、加载成本高的字典?
- python - Djagno 3.0 with Python 3.6.12 1054 遵循 Django 3.0 教程的错误
- mysql - 在 SQLALchemy 中使用 load_only 或 with_entities 的好处有多大?