Контравариантность в универсальных интерфейсах. C#

На англоязычном сайте меня заминусовали и закидали ссылками на темы, которые и вызывают вопросы. Изучаю программирование. Реально хочется понять.

Итак. Я пытаюсь понять какие конкретные прикладные задачи я не смогу решить без контравариантности.

В примере я использую контравариантность в универсальных интерфейсах, чтобы изменить данные внутри экземпляра универсального класса, работающего с более базовым типом, помещая данные менее базового типа через универсальный интерфейс с этим менее базовым типом. А если проще, я наливаю воду в бутылку, которая может содержать любой напиток. Всё по учебнику.

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Bottle<Drink> BottleForDrink = new Bottle<Drink>();

            IAdd<Water> AddWaterInBottlesForDrink = BottleForDrink;
            AddWaterInBottlesForDrink.Add(new Water());

            IGet<Drink> GetDrinkFromBottleForDrink = BottleForDrink;
            Console.WriteLine(GetDrinkFromBottleForDrink.Get());
        }
    }

    class Drink
    {

    }

    class Water : Drink
    {

    }

    class Milk: Drink
    {

    }

    interface IGet<out T>
    {
        T Get();
    }

    interface IAdd<in T>
    {
        void Add(T Capacity);
    }

    class Bottle<T> : IGet<T>, IAdd<T>
    {
        private T Capacity;

        T IGet<T>.Get()
        {
            return this.Capacity;
        }

        void IAdd<T>.Add(T Capacity)
        {
            this.Capacity = Capacity;
        }
    }
}

Но возникла мысль - а зачем мне в данном примере вообще контравариантность и ключевое слово "in". Я убираю ключевое слово "in" и заставляю свой интерфейс работать с более базовым типом - напиток. Теперь я могу точно также изменить данные внутри экземпляра универсального класса, работающего с более базовым типом, помещая данные менее базового типа через интерфейс, но уже без использования контравариации.

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Bottle<Drink> BottleForDrink = new Bottle<Drink>();

            IAdd<Drink> AddWaterInBottlesForDrink = BottleForDrink;
            AddWaterInBottlesForDrink.Add(new Water());

            IGet<Drink> GetDrinkFromBottleForDrink = BottleForDrink;
            Console.WriteLine(GetDrinkFromBottleForDrink.Get());
        }
    }

    class Drink
    {

    }

    class Water : Drink
    {

    }

    class Milk: Drink
    {

    }

    interface IGet<out T>
    {
        T Get();
    }

    interface IAdd<T>
    {
        void Add(T Capacity);
    }

    class Bottle<T> : IGet<T>, IAdd<T>
    {
        private T Capacity;

        T IGet<T>.Get()
        {
            return this.Capacity;
        }

        void IAdd<T>.Add(T Capacity)
        {
            this.Capacity = Capacity;
        }
    }
}

Я снова налил воду в бутылку, которая может содержать любой напиток. Обоими способами я могу налить и молоко, так как бутылка может содержать любой напиток, только в первом случае мне понадобится универсальный интерфейс работающий с молоком.

Мне начали писать что универсальный интерфейс с водой (с менее базовым типом) гарантирует что я смогу налить только воду. Хорошо, но чтобы налить что-то кроме воды, как минимум нужно написать код, в котором я наливаю, например, молоко. Зачем мне писать код про молоко, если мне нужна только вода? По ошибке? Тогда по ошибке я напишу и универсальный интерфейс с молоком и точно так же налью молоко по ошибке, но уже через универсальный интерфейс с контравариантностью. Объясните, пожалуйста... Я прошел 18 глав книги и нигде так не застревал. Я понял принципы по этой теме, думаю что понял...


Ответы (0 шт):