Как добавить универсальный экземпляр в список универсальных объектов - PullRequest
0 голосов
/ 07 октября 2018

У меня есть интерфейс 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 в этот список?

Ответы [ 2 ]

0 голосов
/ 07 октября 2018

Давайте подумаем об этом на секунду.

Вы говорите, что 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
0 голосов
/ 07 октября 2018

Общая ковариация и контравариантность здесь, мы идем!

Итак, у вас есть что-то типа 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 .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...