Общая ковариация и контравариантность здесь, мы идем!
Итак, у вас есть что-то типа 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
звонком вы играете в русскую рулетку и надеетесь, что только что пройденный вами прямоугольник перешел в 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
в качестве типа возврата для любых методов-членов.
Подробнее о ковариации и контравариантности можно найти в документах MSDN .