В случае принятия такого подхода в итоге, как правило, получается несколько специальных делегатов, которые могут никогда не использоваться за рамками текущей задачи (например, MyGenericDelegate, CarEngineHandler и т.д.). Хотя вполне может быть и так, что для проекта требуется специальный уникально именованный делегат, в других ситуациях точное имя типа делегата роли не играет. Во многих случаях просто необходим "какой-нибудь делегат", который принимает набор аргументов и возможно возвращает значение, отличное от void. В таких ситуациях можно применять встроенные в инфраструктуру делегаты Action<> и Func<>. Чтобы удостовериться в их полезности, создайте новый проект консольного приложения по имени ActionAndFuncDelegates.
Обобщенный делегат Action<> определен в пространствах имен System. Его можно использовать для "указания" на метод, который принимает вплоть до void. Вспомните, что поскольку Action<> является обобщенным делегатом, понадобится также указывать типы всех параметров.
Модифицируйте класс Program, определив в нем новый статический метод, который принимает (скажем) три уникальных параметра:
// Это цель для делегата Action<>.
static void DisplayMessage(string msg, ConsoleColor txtColor,
int printCount)
{
// Установить цвет текста консоли.
ConsoleColor previous = Console.ForegroundColor;
Console.ForegroundColor = txtColor;
for (int i = 0; i < printCount; i++)
{
Console.WriteLine(msg);
}
// Восстановить цвет.
Console.ForegroundColor = previous;
}
Теперь вместо построения специального делегата вручную для передачи потока программы методу DisplayMessage() вы можете применять готовый делегат Action<>:
Console.WriteLine("***** Fun with Action and Func *****");
// Использовать делегат Action<> для указания на метод DisplayMessage().
Action
DisplayMessage;
actionTarget("Action Message!", ConsoleColor.Yellow, 5);
Console.ReadLine();
Как видите, при использовании делегата Action<> не нужно беспокоиться об определении специального типа делегата. Тем не менее, как уже упоминалось, тип делегата Action<> позволяет указывать только на методы, возвращающие void. Если необходимо указывать на метод, имеющий возвращаемое значение (и нет желания заниматься написанием собственного типа делегата), тогда можно применять тип делегата Func<>.
Обобщенный делегат Funс<> способен указывать на методы, которые (подобно Action<>) принимают вплоть до 16 параметров и имеют специальное возвращаемое значение. В целях иллюстрации добавьте в класс Program новый метод:
// Цель для делегата Func<>.
static int Add(int x, int y)
{
return x + y;
}
Ранее в главе был построен специальный делегат BinaryOp для "указания" на методы сложения и вычитания. Теперь задачу можно упростить за счет использования версии Func<>, которая принимает всего три параметра типа. Учтите, что Func<> Program также определен следующий метод:
static string SumToString(int x, int y)
{
return (x + y).ToString();
}
Вызовите эти методы:
Func
int result = funcTarget.Invoke(40, 40);
Console.WriteLine("40 + 40 = {0}", result);
Func
string sum = funcTarget2(90, 300);
Console.WriteLine(sum);
С учетом того, что делегаты Action<> и Func<> могут устранить шаг по ручному определению специального делегата, вас может интересовать, должны ли они применяться всегда. Подобно большинству аспектов программирования ответ таков: в зависимости от ситуации. Во многих случаях Action<> и Func<> будут предпочтительным вариантом. Однако если необходим делегат со специальным именем, которое, как вам кажется, помогает лучше отразить предметную область, то построение специального делегата сводится к единственному оператору кода. В оставшихся материалах книги вы увидите оба подхода.