Стандартные делегаты: Action, Predicate, Func

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

Делегат Action (действие)

Этот делегат инкапсулирует метод, который не возвращает значений. Такой делегат обычно используется для передачи метода в котором производятся какие-либо действия, но сам метод не возвращает никаких значений. Описание такого делегата выглядит следующим образом:

public delegate void Action();

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

public delegate void Action<in T>(T obj);
public delegate void Action<in T1,in T2>(T1 arg1, T2 arg2);
//...
public delegate void Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16>

такие делегаты принимают от одного до шестнадцати параметров.  Например, в одной из частей этой главы мы разрабатывали класс калькулятора, который выглядел вот так:

public delegate void Action(Calculator calc);
public delegate double Operation(double x, double y);

public class Calculator
{
    public double X { get; set; }
    public double Y { get; set; }

    Operation? operation;

    public void Calculate(OperationType type)
    {
        operation = SetOperation(type);
        Console.WriteLine(operation(X, Y));
    }

    private static Operation SetOperation(OperationType type)
    {
        switch (type)
        {
            case OperationType.Add: return (x, y) => x + y;
            case OperationType.Multiple: return (x, y) => x * y;
            case OperationType.Divide: return (x, y) => x / y;
            default: return (x, y) => (x + y);
        }
    }

    public void Configure(Action configure)
    {
        configure(this);
    }
}

В методе Configure() мы использовали делегат, который как раз-таки ничего не возвращает, но принимает один параметр — объект типа Calculator. Следовательно, ничего нам не мешает воспользоваться универсальным делегатом Action<T> и переписать код метода Configure() следующим образом:

public void Configure(Action<Calculator> configure)
{
    configure(this);
}

при этом удалив объявление своего делегата Action(Calculator calc) так как он нам уж не потребуется. Аналогичным образом мы можем использовать и другие варианты делегата Action.

Делегат Predicate

Этот делегат принимает один параметр и всегда возвращает значение bool:

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

Такой делегат удобно использовать для сравнения каких-либо значений. Например, воспользуемся этим делегатом в нашем калькуляторе:

Calculator calculator = new Calculator();

calculator.Configure((calc) => 
{ 
    calc.X = 10; 
    calc.Y = 0; 
});

calculator.Calculate(OperationType.Add, calc => true);
calculator.Calculate(OperationType.Multiple, calc => true);
calculator.Calculate(OperationType.Divide, calc => calc.Y > 0);


public enum OperationType { Add, Multiple, Divide}

public class Calculator
{
    public double X { get; set; }
    public double Y { get; set; }

    Operation? operation;

    public void Calculate(OperationType type, Predicate<Calculator> canCalculate)
    {
        if (canCalculate(this))
        {
            operation = SetOperation(type);
            Console.WriteLine(operation(X, Y));
        }
        else
        {
            Console.WriteLine("Невозможно выполнить операцию");
        }
    }

    private static Operation SetOperation(OperationType type)
    {
        switch (type)
        {
            case OperationType.Add: return (x, y) => x + y;
            case OperationType.Multiple: return (x, y) => x * y;
            case OperationType.Divide: return (x, y) => x / y;
            default: return (x, y) => (x + y);
        }
    }

    public void Configure(Action<Calculator> configure)
    {
        configure(this);
    }
}

Здесь метод Calculate() в качестве второго параметра принимает делегат Predicate<Calculator>. Так как у нас в калькуляторе выполняются действия сложения, умножения и деления, то нам необходимо каким-либо образом проверить не произойдет ли при выполнении операции какой-лио ошибки. В нашем случае — деления на ноль. Исходя из результата, который вернет нам делегат, мы либо выполняем расчёт, либо выводим сообщение об ошибке:

public void Calculate(OperationType type, Predicate<Calculator> canCalculate)
{
    if (canCalculate(this))
    {
        operation = SetOperation(type);
        Console.WriteLine(operation(X, Y));
    }
    else
    {
        Console.WriteLine("Невозможно выполнить операцию");
    }
}

При вызове этого метода мы передаем во втором параметре лямбда-выражения, которые и будут преобразованы в тип делегата:

calculator.Calculate(OperationType.Add, calc => true); //сложение пройдет без ошибки
calculator.Calculate(OperationType.Multiple, calc => true); //умножить мы также можем любые вещественные числа
calculator.Calculate(OperationType.Divide, calc => calc.Y > 0);//на ноль делить не можем - проверяем значение Y

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

10
0
Невозможно выполнить операцию

Делегат Func

Такой делегат может принимать от 0 до шестнадцати параметров и возвращает какое-либо значение.

public delegate TResult Func<out TResult>();
public delegate TResult Func<in T,out TResult>(T arg);
//...
public delegate TResult Func<in T1,in T2,in T3,in T4,in T5,in T6,in T7,in T8,in T9,in T10,in T11,in T12,in T13,in T14,in T15,in T16,out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);

Первый универсальный параметр в таком делегате — это тип возвращаемого значения. Пример использования этого делегата можно продемонстрировать, опять же, на примере нашего калькулятора:

public class Calculator
{
    public double X { get; set; }
    public double Y { get; set; }

    Func<double, double, double> operation;

    public void Calculate(OperationType type, Predicate<Calculator> canCalculate)
    {
        if (canCalculate(this))
        {
            operation = SetOperation(type);
            Console.WriteLine(operation(X, Y));
        }
        else
        {
            Console.WriteLine("Невозможно выполнить операцию");
        }
    }

    private static Func<double, double, double> SetOperation(OperationType type)
    {
        switch (type)
        {
            case OperationType.Add: return (x, y) => x + y;
            case OperationType.Multiple: return (x, y) => x * y;
            case OperationType.Divide: return (x, y) => x / y;
            default: return (x, y) => (x + y);
        }
    }

    public void Configure(Action<Calculator> configure)
    {
        configure(this);
    }
}

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

public delegate TResult Func<in T1,in T2,out TResult>(T1 arg1, T2 arg2);

Этот тип делегата мы используем для задания операции:

Func<double, double, double> operation;

и, следовательно, как результат метода SetOperation():

private static Func<double, double, double> SetOperation(OperationType type)
{
    switch (type)
    {
        case OperationType.Add: return (x, y) => x + y;
        case OperationType.Multiple: return (x, y) => x * y;
        case OperationType.Divide: return (x, y) => x / y;
        default: return (x, y) => (x + y);
    }
}

Таким образом мы заменили все наши типы делегатов на универсальные делегаты, уже имеющиеся в .NET. При этом, на работе приложения такая замена никак не отразилась, но код приложения стал более коротким.

Итого

В .NET определен ряд стандартных делегатов, которые мы можем использовать при разработке своих приложений — это делегаты Action, Predicate и Func. Делегаты Action и Func могут принимать от ноля до шестнадцати параметров. Стандартные делегаты активно используются в различных библиотеках C#. Как мы увидим далее, такие делегаты нашли широкое применение в таком механизме как LINQ.

 

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии