none
Generic Типы и UpCast к базовому интерфейсному типу(ковариантность обобщений) RRS feed

  • Вопрос

  • Всем привет, имеется следующий код:

     public abstract class Shape { }
        public class Circle : Shape { }
    
        public interface IContainer<T>
        {
            T Figure { get; set; }
        }
    
        public class Container<T> : IContainer<T>
        {
            public T Figure { get; set; }
    
            public Container(T figure)
            {
                this.Figure = figure;
            }
        }
    
        class Program
        {
            static void Main()
            {
                Circle circle = new Circle();
    
                IContainer<Circle> container = new Container<Circle>(circle);
    
                Console.WriteLine(container.Figure.ToString());
    
                // Delay.
                Console.ReadKey();
            }
        }

    Итак, вкратце  -  что я понял в данном примере. 

    В данном примере мы сперва создаем абстрактный класс с именем Shape{}, затем мы создаем конкретный класс с именем Circle - который наследуется от абстрактного класса shape.

    далее мы создаем открытый интерфейс с именем IContainer - параметризированный  -                                      указателем места заполнения типом Т ,  и в теле данного интерфейса, мы создаем абстрактное авто-свойство с именем Figure  - типа указателя места заполнения типа Т.

    Далее vss создаем класс с именем Container - который реализует интерфейс IContainer, который в свою очередь(интерфейс) параметризирован типом места заполнения типом Т. 

    В теле класса Container мы реализуем абстрактное свойство Figure, и после этого мы создаем конструктор, который принимает один аргумент типа указателя места заполнения типом Т, и в теле этого конструктора мы авто свойству Figure присваиваем значение аргумента данного конструктора нашему свойству.

    Далее, в классе Program, в теле метода Main(), мы создаем экземпляр класса Circle - который как мы помним наследуется от абстрактного класса Shape.

    На строке:

    IContainer<Circle> container = new Container<Circle>(circle);

    - мы создаем переменную с именем container - типа базового интерфейсного типа IContainer, а параметр типа места заполнения типом Т у этого базового интерфейсного типа мы "закрываем" нашим типом Circle.

    Условно разделим приведенную ниже строку на две части:

    IContainer<Circle> container = new Container<Circle>(circle);

    (подчеркнутая - первая часть , жирная - вторая часть

     - в итоге мы ожидаем создаваемый во второй части строки - экземпляр класса Container - ожидаем привести его к базовому интерфейсному типу Icontainer, но в самом созданном экземпляре будет хранится свойство того типа, которым мы "закрываем" место заполнения типом Т при создании данного экземпляра - а т.к в данном случае мы "закрыли" при создании экземпляра типом Circle, то в данном случае внутри данного экземпляра будет храниться свойство Figure типа Circle(а точнее реализация этого свойства из конкретного класса Container наследуемого от интерфейса IContainer)

    Вот мой вопрос: с абстрактным классом Shape{} и конкретным классом Circle{} - наследующим его  - все понятно.

    Затем, кто-нибудь объясните толком и по человечески что такое интерфейс и для чего он нужен (нет, я конечно его изучал, но толком не понял; вот например про делегаты мне простым языком объяснили так  - "Делегаты — это способ сделать следующее: взять произвольный кусок кода, упаковать его в объект (экземпляр делегата) и вызвать этот кусок кода в любом месте, куда получится передать ссылку на наш экземпляр делегата." - может это и не совсем правильно, но это понятно!)

    С авто свойством, его реализацией и конструктором - ине тоже все понятно.

    также не понятно зачем автор курса говорит: "но в самом созданном экземпляре будет хранится свойство" - с какого перепугу? Т.е как я догоадываюсь(но не понимаю навернякка) - в экземпляре хранится то, что переданно в него конструктором(в нашем случае пользовательским) - а т.к аргумент конструктора через ключнвое слово this принимает значение метода set, то косвинно  (чере конструктор) мы как бы принимаем в экземпляр значение свойства.

    И мой самый главный вопрос также вытекает из "что такое интерфейсы - толком" - непонятно зачем мы передаем конструктору уже готовый экземпляр класса Circle, - т.е неужели мы его(этот экземпляр передаваемый как аргумент конструктора) приводим к типу этого интерфейса.

    напоследок вот картинка, на которой я пытался разобраться в ситуации:

    18 июня 2016 г. 19:31

Ответы

  • Это одна из основ ООП (Полиморфизм)

    Посмотрите пример для чего он нужен по ссылке Шаблон стратегия


    Mak Arti

    20 июня 2016 г. 16:06
  • С интерфейсами дело какое - обычно когда пишешь код один и для себя, то многих вещей не можешь понять. Например, зачем делать поле класса private (или как там на С#), если ты и так знаешь, что за пределами этого класса это поле использовать нельзя. А вот когда с твоими классами работают другие ты обязан позаботится, что бы нужные поля были закрыты. Так же и с интерфейсами, их легче понимать, когда мыслишь как часть команды "а как заставить другого программиста делать именно так а не иначе?"

    Пример: Есть программа, которая выводит возраст людей из коллекции. Проблема в том, что каждый человек это класс и создают эти классы разные программисты. Все что они знают про создание класса, это то, что он передается в качестве параметра object вашему методу GetAge. И вот тут возникает проблема. Один запихнул возраст в поле Age, а другой в MyAge. Откуда ваш метод знает в каком поле искать? Хотя он и об этих полях не узнает, ведь он получил object и понятие не имеет к какому типу его приводить. Заставить всех написать одинаковы класс? А если у кого то есть поле, которого нет у других, ведь люди все таки разные, тогда и классы у всех получатся разные.

    Меняем правила и теперь ваш метод GetAge может принимать только наследника от вашего интерфейса, где вы задали поле отвечающее за возраст. Все! Классы наследники могут иметь еще кучу других полей и методов, но при попадании в ваш метод все отработает как надо и вы получите именно то, что задумали.

    Как это использовать в самостоятельном коде? В маленьком проекте может и не придется. Чаще всего собственные интерфейсы (лично у меня) пригождаются для межпроектовского взаимодействия. Например в проектах "клиент-сервер". Типизируя определенные участки кода, становится проще в нем разбираться, когда его становится очень много и тем более, когда он принадлежит разным проектом одного решения.


    VB.Net - WPF, UWP


    20 июня 2016 г. 16:53
    Отвечающий

Все ответы

  • Это одна из основ ООП (Полиморфизм)

    Посмотрите пример для чего он нужен по ссылке Шаблон стратегия


    Mak Arti

    20 июня 2016 г. 16:06
  • С интерфейсами дело какое - обычно когда пишешь код один и для себя, то многих вещей не можешь понять. Например, зачем делать поле класса private (или как там на С#), если ты и так знаешь, что за пределами этого класса это поле использовать нельзя. А вот когда с твоими классами работают другие ты обязан позаботится, что бы нужные поля были закрыты. Так же и с интерфейсами, их легче понимать, когда мыслишь как часть команды "а как заставить другого программиста делать именно так а не иначе?"

    Пример: Есть программа, которая выводит возраст людей из коллекции. Проблема в том, что каждый человек это класс и создают эти классы разные программисты. Все что они знают про создание класса, это то, что он передается в качестве параметра object вашему методу GetAge. И вот тут возникает проблема. Один запихнул возраст в поле Age, а другой в MyAge. Откуда ваш метод знает в каком поле искать? Хотя он и об этих полях не узнает, ведь он получил object и понятие не имеет к какому типу его приводить. Заставить всех написать одинаковы класс? А если у кого то есть поле, которого нет у других, ведь люди все таки разные, тогда и классы у всех получатся разные.

    Меняем правила и теперь ваш метод GetAge может принимать только наследника от вашего интерфейса, где вы задали поле отвечающее за возраст. Все! Классы наследники могут иметь еще кучу других полей и методов, но при попадании в ваш метод все отработает как надо и вы получите именно то, что задумали.

    Как это использовать в самостоятельном коде? В маленьком проекте может и не придется. Чаще всего собственные интерфейсы (лично у меня) пригождаются для межпроектовского взаимодействия. Например в проектах "клиент-сервер". Типизируя определенные участки кода, становится проще в нем разбираться, когда его становится очень много и тем более, когда он принадлежит разным проектом одного решения.


    VB.Net - WPF, UWP


    20 июня 2016 г. 16:53
    Отвечающий