Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
class Fruit
{
public void Eat()
{
Console.WriteLine("You ate fruit!");
}
}
class Orange : Fruit {}
class Program
{
static void Main(string[] args)
{
// создаём объект делегата, который принимает фрукт и возвращает void
Action<Fruit> actionWithFruit = fruit => fruit.Eat();
// создаём список из трёх апельсинов
List<Orange> oranges = new List<Orange> { new Orange(), new Orange(), new Orange() };
// по факту мы должны передать в метод ForEach объект делгата Action<Orange>,
// который принимает апельсин и возвращает void, но благодаря контравариантности
// можем передать Action<Fruit> т.е. записываем в переменную типа Action<Orange> объект типа Action<Fruit>
// контравариантность переварачивает порядок наследования
// Orange : Fruit => Action<Fruit> : Action<Orange>
// Fruit fruit = new Orange();
// Action<Orange> action = new Action<Fruit>(fruit => fruit.Eat());
oranges.ForEach(actionWithFruit);
}
}
Fruit[] fruits = new Orange[10];
List<Fruit> fruits = new List<Orange>(); // ошибка времени компиляции
IEnumerable<Orange> oranges = new List<Orange>();
IEnumerable<Fruit> fruits = oranges;
// творим из фрукта апельсин ;)
static Orange Upgrade(Fruit fruit) { return new Orange(); }
// обобщённый тип делегата для указания на методы
// принимающие TIn и возвращающие TOut
delegate TOut Func<TIn, TOut>(TIn smth);
static void Main(string[] args)
{
// это обычно т.к. Upgrade принимает Fruit и возвращает Orange
// а делегат как раз указывает на такие функции
Func<Fruit, Orange> action = Upgrade;
// используется и ковариантность и контравариантность т.к.
// Upgrade принимает Fruit и возвращает Orange
// а делегат указывает на функции принимающие Orange и возвращающие Fruit
Func<Orange, Fruit> action2 = Upgrade;
}
Func<Fruit, Orange> action = fruit => new Orange();
Func<Orange, Fruit> action2 = action;
interface IMagic<in TIn, out TOut>
{
TOut DoMagic(TIn smth);
}
class Magic : IMagic<Fruit, Orange>
{
public Orange DoMagic(Fruit smth)
{
return new Orange();
}
}
class Program
{
static void Main(string[] args)
{
// это обычно т.к. Magic реализует интерфейс IMagic<Fruit, Orange>
IMagic<Fruit, Orange> i1 = new Magic();
// используется и ковариантность и контравариантность т.к. записываем
// IMagic<Fruit, Orange> в IMagic<Orange, Fruit>
IMagic<Orange, Fruit> i2 = new Magic();
}
}
Стоит запомнить, что ковариантность и контравариантность могут вызывать ошибки времени выполнения. Для их устранения требуется вводить определённые ограничения. Компиляторы, как правило, такие ограничения не вводят.
List<Device> devices = new List<Keyboard>();
devices.Add(new Device()); // ошибка времени выполнения
List<T> является неизменяемым типом. Это не запрещает, приведя List<Keyboard> к List<Device>, записать в список объект типа Mouse, что вызовет ошибку времени выполнения. Device) и его предков (Object). Получается мы можем добавить такое ограничение и для изменяемого (мутабельного) типа, что сделает его типобезопасным.public interface ISolver<out TInputData,out TAnswerData>
{
TAnswerData Solve(TInputData data);
}
public class BinomSolver : ISolver<BinomTaskData,BinomAnswerData>
{
public override BinomAnswerData Solve(SolverDataBase input)
{
var data = GetParameter(input);
var discrim = Math.Pow(data.B, 2) - 4 * data.A * data.C;
var x1 = (-data.B + Math.Sqrt(discrim)) / (2 * data.A);
var x2 = (-data.B - Math.Sqrt(discrim)) / (2 * data.A);
return new BinomAnswerData(x1, x2);
}
}
public interface ISolver<in TInputData, out TAnswerData>
{
TAnswerData Solve(TInputData data);
}
class Fruit : IMagic<Fruit>
{
public void DoMagic(Fruit smth) { Console.WriteLine("Hello from fruit!"); }
}
class Orange : Fruit { }
interface IMagic<in T>
{
void DoMagic(T smth);
}
static void Main(string[] args)
{
var f = new Fruit();
// не контравариация, пользуемся возможностью приведения потомка к предку
f.DoMagic(new Orange());
// контравариация, записываем IMagic<Fruit> в IMagic<Orange>
// т.е. в обратном порядке к возможностям исходных
// Fruit и Orange (Orange o = new Fruit();)
IMagic<Orange> magic = f as IMagic<Fruit>;
magic.DoMagic(new Orange());
}
"Hello from fruit!". И вот тот второй раз следует рассмотреть. Мы привели в конечном итоге Fruit к IMagic<Orange>. Вызываем метод DoMagic передавая ему Orange, но по факту вызывается метод котрый принимает Fruit и это типобезопасно т.к. допустимо преобразование из Orange в Fruit. class Fruit {}
class Orange : Fruit, IMagic<Orange>
{
public void DoMagic(Orange smth) { Console.WriteLine("Hello from orange!"); }
}
interface IMagic<out T>
{
void DoMagic(T smth);
}
static void Main(string[] args)
{
// ковариация, записываем IMagic<Orange> в IMagic<Fruit>
// т.е. в прямом порядке к возможностям исходных
// Fruit и Orange (Orange o = new Fruit();)
IMagic<Fruit> magic = new Orange() as IMagic<Orange>;
magic.DoMagic(new Fruit());
}
Orange к IMagic<Fruit> (а это было бы возможно т.к. параметр типа ковариантен), есть возможность вызвать метод DoMagic и передать ему либо Fruit либо Orange (т.к. он приводим к Fruit). По факту будет вызыватся DoMagic, который требует Orange и передача ему Fruit вызовет ошибку времени выполнения.
Вариантность в программировании