День сто девяносто третий. #ЗаметкиНаПолях

Заметки по обобщённым типам

2. Вывод типа из аргументов методов

Рассмотрим следующий код:

public static List<T> CopyAtMost<T>(List<T> input, int elements)

{

return input.Take(elements).ToList();

}



var numbers = new List<int>() { 1, 2, 3, 4 };

var firstTwo = CopyAtMost<int>(numbers, 2);



Вам нужен аргумент типа для вызова CopyAtMost, потому что метод имеет параметр типа. Но вам не нужно указывать этот тип аргумента явно. Можно переписать этот код следующим образом:

var firstTwo = CopyAtMost(numbers, 2);

Это точно такой же вызов метода с точки зрения IL, который сгенерирует компилятор. Но аргумент типа int указывать не нужно, компилятор сделал это для вас на основе аргумента для первого параметра метода. Вы используете аргумент List<int> в качестве значения параметра типа List<T>, поэтому T должно быть int. Вывод типа может использовать только аргументы, передаваемые методу, но не тип результата.

Хотя вывод типов применяется только к методам, его можно использовать для более простого создания экземпляров обобщённых типов. Например, рассмотрим семейство типов Tuple, состоящее из необобщённого статического класса Tuple и нескольких обобщённых классов: Tuple<T1>, Tuple<T1, T2>, Tuple<T1, T2, T3> и так далее (до 8). Статический класс имеет набор перегруженных фабричных методов Create:

public static Tuple<T1> Create<T1>(T1 item1)

{

return new Tuple<T1>(item1);

}

public static Tuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2)

{

return new Tuple<T1, T2>(item1, item2);

}

и так далее…

Они выглядят бессмысленно тривиальными, но позволяют использовать вывод типа. То есть, вместо этого:

new Tuple<int, string, int>(10, "x", 20)

Можно написать:

Tuple.Create(10, "x", 20)

Это мощная техника, о которой полезно знать; как правило, её просто реализовать и она может сделать работу с обобщённым кодом намного приятнее.

Разработчики языка постоянно работают над усовершенствованием процесса вывода типа, и порой логика вывода становится достаточно сложной из-за наследований, перегрузок и необязательных параметров. Поэтому надо понимать, что иногда на практике выведение типа может приводить к неожиданным результатам. Например, если вам нужен Tuple<int, object, int>, то из предыдущего вызова Tuple.Create(10, "x", 20) вы его не получите. В этом случае можно использовать либо new Tuple<int, object, int>(10, "x", 20), либо приведение типа Tuple.Create(10, (object)"x", 20). Аналогично с null: Tuple.Create(null, 50) завершится неудачей, но Tuple.Create((string) null, 50) сработает.



Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.