Лямбда-выражения (Руководство по программированию в C#)
Лямбда-выражение анонимная функция, которую можно использовать для создания делегатов или типов дерева выражений.С помощью лямбда-выражений, можно написать локальные функции, которые можно передать в качестве аргументов или возвратить в качестве значения вызовов функций.Лямбда-выражения особенно полезны для составления выражений запросов LINQ.
Для создания лямбда-выражение можно определить входные параметры (если таковые имеются), слева лямбда-оператора => и поместить блок выражений или выписки на другую сторону.Например, лямбда-выражение x => x * x указывает параметр с именем x и возвращает значение x придало в квадрат.Это выражение можно присвоить типу делегата, как показано в следующем примере:
delegate int del(int i);
static void Main(string[] args)
{
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25
}
Создание типа дерева выражений
using System.Linq.Expressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Expression<del> myET = x => x * x;
}
}
}
Оператор => имеет тот же приоритет, что и оператор присваивания (=) и является правоассоциативным.
Лямбда-операторы используются в запросах LINQ на основе методов в качестве аргументов стандартных методов операторов запроса, таких как Where.
При использовании синтаксиса на основе методов для вызова метода Where в классе Enumerable (как это делается в LINQ на объекты и LINQ to XML) параметром является тип делегата System.Func<T, TResult>.Лямбда-выражение — это наиболее удобный способ создания делегата.При вызове такого же метода, к примеру, в классе System.Linq.Queryable (как это делается в LINQ to SQL) типом параметра будет System.Linq.Expressions.Expression<Func>, где Func — это любые делегаты Func с числом параметров ввода не более шестнадцати.Опять же, лямбда-выражения представляют собой самый быстрый способ построения дерева выражений.Лямбда-операторы позволяют вызовам Where выглядеть одинаково, хотя на самом деле объект, созданный из лямбда-выражения, имеет другой тип.
Обратите внимание: в приведенном выше примере сигнатура делегата имеет один неявный параметр ввода типа int и возвращает значение типа int.Лямбда-выражение можно преобразовать в делегат соответствующего типа, поскольку он также имеет один параметр ввода (x) и возвращает значение, которое компилятор может неявно преобразовать в тип int.(Вывод типа более подробно рассматривается в следующих разделах.) Делегат, вызываемый посредством параметра ввода 5, возвращает результат 25.
Лямбда-операторы не разрешены с левой стороны оператора is или as.
Все ограничения, применяемые к анонимным методам, применяются также к лямбда-выражениям.Дополнительные сведения см. в разделе Анонимные методы (Руководство по программированию в C#).
Выражения-лямбды
Лямбда-выражение с выражением с правой стороны называется выражением-лямбдой.Выражения-лямбды широко используются при конструировании Деревья выражений (C# и Visual Basic).Выражения-лямбды возвращают результат выражения и принимают следующую основную форму.
(input parameters) => expression
Если лямбда имеет только один параметр ввода, скобки можно не ставить, во всех остальных случаях они обязательны.Два и более параметра разделяются запятыми и заключаются в скобки:
(x, y) => x == y
Иногда компилятору бывает трудно или даже невозможно определить введенные типы.В этом случае типы можно указать в явном виде, как показано в следующем примере.
(int x, string s) => s.Length > x
Нулевые параметры ввода указываются пустыми скобками.
() => SomeMethod()
Обратите внимание: в примере выше основная часть выражения-лямбды может состоять из вызова метода.Однако при создании деревьев выражений, которые будут использоваться в другом домене, например SQL Server, в лямбда-выражениях не следует использовать вызовы методов.Эти методы не имеют смыслового значения вне контекста среды выполнения .NET.
Лямбды операторов
Лямбда оператора напоминает выражение-лямбду, за исключением того, что оператор (или операторы) заключается в фигурные скобки.
(input parameters) => {statement;}
Основная часть лямбды оператора может состоять из любого количества операторов; однако на практике обычно используется не больше двух-трех.
delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");
Лямбды операторов, как и анонимные методы, не могут использоваться для создания деревьев выражений.
Async Lambdas
Можно легко создать лямбда-выражения и выписки, относятся асинхронная обработка с помощью ключевых слов async и await.Например, в следующем примере Windows Forms содержит обработчик событий, который вызывает метод async и ждет, ExampleMethodAsync.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
// ExampleMethodAsync returns a Task.
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
}
async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
Можно добавить один и тот же обработчик событий, используя async лямбда-выражения.Чтобы добавить этот обработчик, добавьте модификатор async перед списком параметров лямбда-выражения, как показано в следующем примере.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += async (sender, e) =>
{
// ExampleMethodAsync returns a Task.
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
};
}
async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
Дополнительные сведения о создании и использовании методов async см. в разделе Асинхронное программирование с использованием ключевых слов Async и Await (C# и Visual Basic).
Лямбды со стандартными операторами запросов
Многие стандартные операторы запросов имеют параметр ввода, тип которого принадлежит семейству Func<T, TResult> общих делегатов.Делегаты Func<T, TResult> используют параметры типа для определения количества и типа входных параметров, а также тип возвращаемого значения делегата.Делегаты Func очень полезны для инкапсуляции пользовательских выражений, которые применяются к каждому элементу в наборе исходных данных.В качестве примера рассмотрим следующий тип делегата.
public delegate TResult Func<TArg0, TResult>(TArg0 arg0)
Можно создать экземпляр этого делегата как Func<int,bool> myFunc, где int — параметр ввода, а bool — возвращаемое значение.Возвращаемое значение всегда указывается в последнем параметре типа.Func<int, string, bool> определяет делегат с двумя параметрами входа, int и string, и типом возвращаемого значения bool.Следующий делегат Func при вызове возвращает значение true или false, которое показывает, равен ли параметр ввода 5.
Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course
Также лямбда-выражения можно использовать, когда аргумент имеет тип Expression<Func>, например в стандартных операторах запроса, как указано в System.Linq.Queryable.При определении аргумента Expression<Func> лямбда компилируется в дерево выражения.
Стандартный оператор запроса, метод Count, показан ниже.
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Компилятор может вывести тип параметра ввода; также его можно определить явно.Данное лямбда-выражение подсчитывает указанные целые значения (n), которые при делении на два дают остаток 1.
Следующий метод выдает последовательность, которая содержит все элементы массива numbers, влево на 9, поскольку это первый номер в последовательности, которая не соответствует условиям:
var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
В этом примере показано, как определить несколько параметров ввода путем их заключения в скобки.Этот метод возвращает все элементы в массиве чисел до того числа, величина которого меньше номера его позиции.Не следует путать лямбда-оператор (=>) с оператором "больше или равно" (>=).
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Вывод типа в лямбда-выражениях
При написании лямбда-выражений обычно не требуется указывать тип параметров ввода, поскольку компилятор может вывести этот тип на основе тела лямбда-выражения, лежащего в его основе типа делегата и других факторов, как описано в спецификации языка C#.Для большинства стандартных операторов запроса первый ввод имеет тип элементов в исходной последовательности.Поэтому, если производится запрос IEnumerable<Customer>, переменная ввода расценивается как объект Customer, а это означает, что у вас есть доступ к его методам и свойствам.
customers.Where(c => c.City == "London");
Используются следующие основные правила для лямбда-выражений.
Лямбда-выражение должно содержать то же число параметров, что и тип делегата.
Каждый параметр ввода в лямбда-выражении должен быть неявно преобразуемым в соответствующий параметр делегата.
Возвращаемое значение лямбда-выражения (если таковое имеется) должно быть неявно преобразуемым в возвращаемый тип делегата.
Обратите внимание: лямбда-выражения сами по себе не имеют типа, поскольку система общих типов не имеет внутреннего принципа "лямбда-выражения". Однако иногда бывает удобно оперировать понятием "типа" лямбда-выражения.При этом под типом понимается тип делегата или тип Expression, в который преобразуется лямбда-выражение.
Область действия переменной в лямбда-выражениях
Лямбда-выражения могут ссылаться на внешние переменные, попадающие в область действия включающего их метода или типа, в котором определено это выражение.Переменные, захваченные таким способом, сохраняются для использования в лямбда-выражениях даже в том случае, если эти переменные иначе попадают за границы области действия и уничтожаются сборщиком мусора.Внешняя переменная должна быть определенным образом назначена, прежде чем она сможет использоваться в лямбда-выражениях.В следующем примере показаны эти правила.
delegate bool D();
delegate bool D2(int i);
class Test
{
D del;
D2 del2;
public void TestMethod(int input)
{
int j = 0;
// Initialize the delegates with lambda expressions.
// Note access to 2 outer variables.
// del will be invoked within this method.
del = () => { j = 10; return j > input; };
// del2 will be invoked after TestMethod goes out of scope.
del2 = (x) => {return x == j; };
// Demonstrate value of j:
// Output: j = 0
// The delegate has not been invoked yet.
Console.WriteLine("j = {0}", j); // Invoke the delegate.
bool boolResult = del();
// Output: j = 10 b = True
Console.WriteLine("j = {0}. b = {1}", j, boolResult);
}
static void Main()
{
Test test = new Test();
test.TestMethod(5);
// Prove that del2 still has a copy of
// local variable j from TestMethod.
bool result = test.del2(10);
// Output: True
Console.WriteLine(result);
Console.ReadKey();
}
}
Следующие правила применимы к области действия переменной в лямбда-выражениях.
Захваченная переменная не будет уничтожена сборщиком мусора до тех пор, пока делегат, который на нее ссылается, не выйдет за границы области.
Переменная, введенная в лямбда-выражение, невидима во внешнем методе.
Лямбда-выражение не может непосредственно захватывать параметры ref или out из включающего их метода.
Оператор Return в лямбда-выражении не вызывает возвращение значения методом.
Лямбда-выражение не может содержать оператор goto, оператор break или оператор continue, целевой объект которого находится вне тела либо в теле содержащейся анонимной функции.
Спецификация языка C#
Дополнительные сведения см в Спецификация языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.
Важная глава книги
Delegates, Events, and Lambda Expressions в C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers
См. также
Ссылки
Анонимные методы (Руководство по программированию в C#)
Основные понятия
Руководство по программированию на C#
Деревья выражений (C# и Visual Basic)