Func и Action – обобщенные делегаты

Просто о NET | создано: 18.11.2010 | опубликовано: 22.04.2014 | обновлено: 13.01.2024 | просмотров: 12681

Func и Action обобщенные делегаты были введены в .NET Framework начиная с NET 3.5. Они обеспечивают гибкость делегатов с общими параметрами. Делегат Func предназначен для инкапсуляции метода, который принимает в качестве параметров от нуля для четырех аргументов и возвращает значение. Что же касается делегата Action, то единственное отличие его от Func – это то, что Action возвращает процедуру.

Обобщенные делегаты

Func и Action обобщенные делегаты были введены в .NET Framework начиная с версии 3.5. Они обеспечивают гибкость делегатов с общими параметрами, которые могут быть использованы для различных целей, в том числе успешно лямбда-выражений к параметрам метода. Делегат Func предназначен для инкапсуляции метода, который принимает в качестве параметров от нуля дл четырех аргументов и возвращает значение. Что же касается делегата Action, то единственное отличие его от Func – это то, что Action возвращает процедуру.

Новые делегаты могут использоваться, чтобы сократить количество делегатов (извините за каламбур). В ситуациях, где Вы нуждались бы в делегате, который соответствует одному из предопределенных делегатов Func или Действия, Вы можете решить использовать встроенную версию.

Ключевая причина, подталкивающая к использованию обобщенных делегатов - это их связь с лямбда-выражениями. Базовый тип каждого лямбда-выражения является одним из обобщенных методов-делегатов.

Использование Func

Инкапсулирует метод без параметров и возвращает значение типа, указанного в параметре TResult. Этот делегат можно использовать для представления метода, который можно передавать в качестве параметра без явного объявления пользовательского делегата. Метод должен соответствовать заданной этим делегатом сигнатуре метода. Это означает, что инкапсулируемый метод должен не иметь параметров, но возвращать значение. Сигнатуры (все пять) показаны ниже:

public delegate TResult Func<TResult>()
public delegate TResult Func<T, TResult>(T arg)
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2)
public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3)
public delegate TResult Func<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4)

Самый простой вариант Func это Func<TResult>. Этот делегат представляет метод, который возвращает значение и при этом не имеет никаких параметров.  Новый делегат использует анонимный метод, который возвращает double значение. Код ниже демонстрирует это.

Func<double> doub = delegate { return 34.4; };
Console.WriteLine(doub()); 

Такую же функциональность можно реализовать при помощи лямбда-выражений:

Func<double> doub = ()=> { return 34.4; };
Console.WriteLine(doub());

А теперь проделаем тоже самое, но только уже с параметрами:

Func<byte, byte, byte, int> plus = delegate(byte q, byte w, byte e) { return q + w + e; };
Console.WriteLine(plus(1, 2, 3));

А теперь тоже самое, только уже через лямбда-выражения:

Func<byte, byte, byte, int> plus2 = (byte q, byte w, byte e) => { return q + w + e; };
Console.WriteLine(plus2(4, 5, 6));

Использование Action

Также как и Func делегат Action имеет пять перегрузок по сигнатурам, которые позволяют использовать до четырех параметров, но при этом Action не возвращает никаких значений. А самое интересное, что параметром для Action может быть любой тип для любого из параметров в сигнатуре.

public delegate void Action()
public delegate void Action<T, >(T arg)
public delegate void Action<T1, T2>(T1 arg1, T2 arg2)
public delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
public delegate void Action<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4)

Простой пример использования:

Action showMessage = delegate { Console.WriteLine("Hello!"); };
showMessage();

и через лямбда-выражения:

Action showMessaga2 = () => { Console.WriteLine("Hello world!"); };
showMessaga2();

Теперь посмотрите, как использовать в делегате Action параметры:

Action<string> sm1 = delegate(string m) { Console.WriteLine(m); };
sm1("Wow");

и через лямбда-выражения:

Action<string, string> sm2 = (txt, txt2) => { Console.WriteLine(String.Format("{0} {1}!", txt, txt2)); };
sm2("Привет", "Мир");

Просто всё как! Не правда ли?!

Пример использования делегатов

Делегат Func обычно используется как тип для параметров методов, которые принимают лямбды-выражения. Они включают стандартные операторы запросов LINQ и подобные методы, которые Вы используете в своих собственных проектах. Покажу как работает на заключительном примере этой статьи.

Метод Fruit определяет строковый массив содержащий наименования фруктов. Метод возвращает фильтрованные фрукты на основании Func делегата, который указан в сигнатуре метода. Func делегат инкапсулирует метод, который принимает один строковый параметр и возвращает булево значение. Мы можем использовать результат метода Fruit в операторе foreach для вывода фруктов, которые удовлетворяют условию запросу where.

private static string[] Fruit(Func<string, bool> filter)
{
    string[] fruit = new string[]
    {
        "Манго", "Банан", "Вишня", "Чернослив", "Ежевика",
        "Фига", "Грейпфрукт", "Черника", "Лимон", "Яброко"
    };
    return fruit.Where(filter).ToArray();
}

Для вывода на экран воспользуемся методом, который был реализован в одном из примеров. Результатом вызова метода:

string[] fruits = Fruit(f => f.StartsWith("Ч"));
foreach (string item in fruits)
{
   sm1(item);
}

будет:

Чернослив
Черника

Или вот другой пример вызова метода Fruit:

string[] fruits2 = Fruit(f => f.Length <6);
foreach (string item in fruits2)
{
    sm1(item);
}

на экран будет выведено:

Манго
Банан
Вишня
Фига
Лимон

Всё элементарно и просто… Не правда ли? :)