Контравариантность в универсальных интерфейсах. 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 глав книги и нигде так не застревал. Я понял принципы по этой теме, думаю что понял...