Бөлісу құралы:


Делегаты и лямбда-выражения

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

using System;
using System.Linq;

public class Program
{
    public delegate string Reverse(string s);

    static string ReverseString(string s)
    {
        return new string(s.Reverse().ToArray());
    }

    static void Main(string[] args)
    {
        Reverse rev = ReverseString;

        Console.WriteLine(rev("a string"));
    }
}
  • Строка public delegate string Reverse(string s); создает тип делегата метода, который принимает строковый параметр, а затем возвращает строковый параметр.
  • Метод static string ReverseString(string s) , имеющий точно тот же список параметров и тип возвращаемого значения, что и определенный тип делегата, реализует делегат.
  • В строке Reverse rev = ReverseString; показано, что метод можно назначить переменной соответствующего типа делегата.
  • В Console.WriteLine(rev("a string")); строке показано, как использовать переменную типа делегата для вызова делегата.

Чтобы упростить процесс разработки, .NET включает набор типов делегатов, которые программисты могут повторно использовать и не должны создавать новые типы. Эти типы — Func<>, Action<> и Predicate<>, и они могут использоваться без необходимости определять новые типы делегатов. Существуют некоторые различия между тремя типами, которые связаны с способом их использования:

  • Action<> используется, если требуется выполнить действие с помощью аргументов делегата. Метод, который он инкапсулирует, не возвращает значение.
  • Func<> обычно используется при наличии преобразования, то есть необходимо преобразовать аргументы делегата в другой результат. Проекции являются хорошим примером. Метод, который он инкапсулирует, возвращает указанное значение.
  • Predicate<> используется, когда необходимо определить, соответствует ли аргумент условию делегата. Также это можно записать как Func<T, bool>, что означает, что метод возвращает значение типа boolean.

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

using System;
using System.Linq;

public class Program
{
    static string ReverseString(string s)
    {
        return new string(s.Reverse().ToArray());
    }

    static void Main(string[] args)
    {
        Func<string, string> rev = ReverseString;

        Console.WriteLine(rev("a string"));
    }
}

В этом простом примере метод, определенный вне Main метода, кажется немного лишним. Платформа .NET Framework 2.0 представила концепцию анонимных делегатов, что позволяет создавать "встроенные" делегаты, не указывая дополнительный тип или метод.

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

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main(string[] args)
    {
        List<int> list = new List<int>();

        for (int i = 1; i <= 100; i++)
        {
            list.Add(i);
        }

        List<int> result = list.FindAll(
          delegate (int no)
          {
              return (no % 2 == 0);
          }
        );

        foreach (var item in result)
        {
            Console.WriteLine(item);
        }
    }
}

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

Однако даже с этим подходом существует еще много кода, который мы можем выбросить. Вот где лямбда-выражения вступают в игру. Лямбда-выражения, или просто "лямбды", были введены в C# 3.0 в качестве одного из основных строительных блоков Language Integrated Query (LINQ). Это просто более удобный синтаксис для использования делегатов. Они объявляют список параметров и тело метода, но не имеют собственной формальной идентичности, если только они не назначены делегату. В отличие от делегатов, они могут быть непосредственно назначены в качестве правой стороны регистрации событий или в различных предложениях и методах LINQ.

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

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main(string[] args)
    {
        List<int> list = new List<int>();

        for (int i = 1; i <= 100; i++)
        {
            list.Add(i);
        }

        List<int> result = list.FindAll(i => i % 2 == 0);

        foreach (var item in result)
        {
            Console.WriteLine(item);
        }
    }
}

В предыдущем примере используется лямбда-выражение i => i % 2 == 0. Опять же, это просто удобный синтаксис для использования делегатов. Что происходит под обложкой, похоже на то, что происходит с анонимным делегатом.

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

public MainWindow()
{
    InitializeComponent();

    Loaded += (o, e) =>
    {
        this.Title = "Loaded";
    };
}

Оператор += в этом контексте используется для подписки на событие. Дополнительные сведения см. в разделе Как подписаться и отменить подписку на события.

Дальнейшее чтение и ресурсы