Поделиться через


Лямбда-выражения (Руководство по программированию в 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#)

is (Справочник по C#)

Другие ресурсы

LINQ

Рекурсивные лямбда-выражения