Поделиться через


Строго типизированные делегаты

Предыдущий

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

Абстрактный класс делегата предоставляет инфраструктуру для слабой взаимозависимости (loose coupling) и вызовов. Конкретные типы делегата становятся гораздо полезнее, поскольку включают и обеспечивают безопасность типов для методов, добавляемых в список вызовов для объекта делегата. Компилятор создает эти методы, если вы используете ключевое слово delegate и определяете конкретный тип делегата.

В результате новые типы делегатов создаются всякий раз, когда возникает необходимость в новой сигнатуре метода. Со временем эта работа может стать слишком громоздкой. Для каждого нового компонента требуются новые типы делегатов.

К счастью, это необязательно. Платформа .NET Core содержит несколько типов, которые можно использовать всякий раз, когда вам нужно делегировать типы. Эти определения универсальны, поэтому всякий раз, когда вам нужно объявить новый метод, можно объявить настройку.

Первый из этих типов — это тип Action и несколько вариантов:

public delegate void Action();
public delegate void Action<in T>(T arg);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
// Other variations removed for brevity.

Модификатор in для аргумента универсального типа рассматривается в статье о ковариации.

Существуют варианты делегата Action, содержащие до 16 аргументов, такие как Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16>. Очень важно, чтобы для каждого из аргументов делегата в этих определениях использовались универсальные аргументы — это дает максимальную гибкость. Аргументы метода могут быть одного типа, однако это необязательно.

В качестве типа делегата используйте один из типов Action с типом возвращаемого значения void.

Платформа включает также несколько типов универсальных делегатов, которые можно использовать в качестве типов делегатов, возвращающих значения:

public delegate TResult Func<out TResult>();
public delegate TResult Func<in T1, out TResult>(T1 arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
// Other variations removed for brevity

Модификатор out для аргумента универсального типа результата рассматривается в статье о ковариации.

Существуют варианты делегата Func, содержащие до 16 входных аргументов, такие как Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>. Как правило, тип результата должен быть последним параметром типа во всех объявлениях Func.

В качестве типа делегата, возвращающего значение, используйте один из типов Func.

Существует также специальный тип Predicate<T>, предназначенный для делегатов, возвращающих тест одного значения:

public delegate bool Predicate<in T>(T obj);

Можно заметить, что для каждого типа Predicate существует структурно эквивалентный тип Func, например:

Func<string, bool> TestForString;
Predicate<string> AnotherTestForString;

Можно подумать, что два эти типа эквивалентны. Это не так. Эти две переменные не заменяют друг друга. Переменную одного типа нельзя назначить другому типу. В системе типов C# используются имена определенных типов, а не структуры.

Все эти определения типов делегатов в библиотеке .NET Core должны означать, что новый тип делегата для каждой создаваемой функции, которой требуются делегаты, создавать не нужно. Эти универсальные определения должны предоставлять типы делегатов, необходимые в большинстве ситуаций. Можно просто создать экземпляр одного из этих типов с необходимыми параметрами типа. Если алгоритмы можно сделать универсальными, эти делегаты можно использовать как универсальные типы.

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

В следующей статье представлено несколько общих шаблонов для работы с делегатами на практике.

Далее