Делегаты и лямбда-выражения
Делегат определяет тип, представляющий ссылки на методы с определенным списком параметров и типом возврата. Метод (статический или экземпляр), список параметров и соответствие типа возвращаемого типа можно назначить переменной этого типа, а затем вызвать напрямую (с соответствующими аргументами) или передать в качестве аргумента другому методу, а затем вызвать. В следующем примере показано использование делегата.
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>
, что означает, что метод возвращает логическое значение.
Теперь можно обратиться к приведенному выше примеру и переписать его, используя делегат 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);
}
}
}
Как видно, тело делегата представляет собой набор выражений, как в любом другом делегате. Но вместо использования отдельного определения мы описали его напрямую в нашем вызове метода List<T>.FindAll.
Однако даже при таком подходе остается довольно много кода, от которого можно избавиться. Как раз для этого и могут пригодиться лямбда-выражения. Лямбда-выражения были введены в C# 3.0 в качестве одного из стандартных блоков 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";
};
}
В этом контексте оператор +=
используется для подписки на событие. Дополнительные сведения см. в разделе Практическое руководство. Подписка и отмена подписки на события.