Лямбда-выражения (Руководство по программированию в C#)
Обновлен: Ноябрь 2007
Лямбда-выражение — это анонимная функция, которая содержит выражения и операторы и может использоваться для создания делегатов или типов дерева выражений.
Во всех лямбда-выражениях используется лямбда-оператор =>, который читается как "переходит в". Левая часть лямбда-оператора определяет параметры ввода (если таковые имеются), а правая часть содержит выражение или блок оператора. Лямбда-выражение x => x * x читается как "x переходит в x x раз". Это выражение может быть назначено типу делегата следующим образом:
delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25
Создание типа дерева выражений
using System.Linq.Expressions;
// ...
Expression<del> = x => x * x;
Оператор => имеет тот же приоритет, что и оператор присваивания (=) и является правоассоциативным.
Лямбда-операторы используются в запросах LINQ на основе методов в качестве аргументов стандартных методов операторов запроса, таких как Where и 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#).
Выражения-лямбды
Лямбда-выражение с выражением с правой стороны называется выражением-лямбдой. Выражения-лямбды широко используются при конструировании Деревья выражений. Выражения-лямбды возвращают результат выражения и принимают следующую основную форму.
(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");
Лямбды операторов, как и анонимные методы, не могут использоваться для создания деревьев выражений.
Лямбды со стандартными операторами запросов
Многие стандартные операторы запросов имеют параметр ввода, тип которого принадлежит семейству 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.
Следующий метод создает последовательность, которая содержит все элементы в массиве чисел, идущие до "9", поскольку это первое число в последовательности, не удовлетворяющее условию:
var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
В этом примере показано, как определить несколько параметров ввода путем их заключения в скобки. Этот метод возвращает все элементы в массиве чисел до того числа, величина которого меньше номера его позиции. Не следует путать лямбда-оператор (=>) с оператором "больше или равно" (>=).
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Вывод типа в лямбда-выражениях
При написании лямбда-выражений обычно не требуется указывать тип параметров ввода, поскольку компилятор может вывести этот тип на основе тела лямбда-выражения, лежащего в его основе типа делегата и других факторов, как описано в спецификации языка C# 3.0. Для большинства стандартных операторов запроса первый ввод имеет тип элементов в исходной последовательности. Поэтому, если производится запрос 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#:
- 5.3.3.29 Анонимные функции
См. также
Основные понятия
Руководство по программированию в C#
Ссылки
Анонимные методы (Руководство по программированию в C#)