Лямбда-выражения (Справочник по C#)
Лямбда-выражение используется для создания анонимной функции. Используйте оператор объявления лямбда-выражения для отделения списка параметров лямбда-выражения от исполняемого кода. Лямбда-выражение может иметь одну из двух следующих форм:
Лямбда выражения, имеющая выражение в качестве текста:
Лямбда оператора, имеющая блок операторов в качестве текста:
Чтобы создать лямбда-выражение, необходимо указать входные параметры (если они есть) с левой стороны лямбда-оператора и блок выражений или операторов с другой стороны.
Лямбда-выражение может быть преобразовано в тип делегата. Тип делегата, в который может быть преобразовано лямбда-выражение, определяется типами его параметров и возвращаемым значением. Если лямбда-выражение не возвращает значение, оно может быть преобразовано в один из типов делегата Action ; в противном случае его можно преобразовать в один из типов делегатов Func . Например, лямбда-выражение, которое имеет два параметра и не возвращает значение, можно преобразовать в делегат Action<T1,T2>. Лямбда-выражение, которое имеет два параметра и возвращает значение, можно преобразовать в делегат Func<T,TResult>. В следующем примере лямбда-выражение x => x * x , которое указывает параметр с именем x и возвращает значение x в квадрате, присваивается переменной типа делегата:
Лямбда-выражения можно также преобразовать в типы дерева выражения, как показано в следующем примере:
Лямбда-выражения можно использовать в любом коде, для которого требуются экземпляры типов делегатов или деревьев выражений, например в качестве аргумента метода Task.Run(Action) для передачи кода, который должен выполняться в фоновом режиме. Можно также использовать лямбда-выражения при применении LINQ в C#, как показано в следующем примере:
При использовании синтаксиса на основе методов для вызова метода Enumerable.Select в классе System.Linq.Enumerable (например, в LINQ to Objects и LINQ to XML) параметром является тип делегата System.Func<T,TResult>. При вызове метода Queryable.Select в классе System.Linq.Queryable (например, в LINQ to SQL) типом параметра является тип дерева выражения Expression<Func<TSource,TResult>> . В обоих случаях можно использовать одно и то же лямбда-выражение для указания значения параметра. Поэтому оба вызова Select выглядят одинаково, хотя на самом деле объект, созданный из лямбда-выражения, имеет другой тип.
Выражения-лямбды
Лямбда-выражение с выражением в правой => части оператора называется => . Выражения-лямбды возвращают результат выражения и принимают следующую основную форму.
Текст выражения лямбды может состоять из вызова метода. Но при создании деревьев выражений, которые вычисляются вне контекста поддержки общеязыковой среды выполнения (CRL) .NET, например в SQL Server, вызовы методов не следует использовать в лямбда-выражениях. Эти методы не имеют смысла вне контекста среды CLR .NET.
Лямбды операторов
Лямбда-инструкция напоминает лямбда-выражение, за исключением того, что инструкции заключаются в фигурные скобки:
Тело лямбды оператора может состоять из любого количества операторов; однако на практике обычно используется не более двух-трех.
Лямбда-инструкции нельзя использовать для создания деревьев выражений.
Входные параметры лямбда-выражения
Входные параметры лямбда-выражения заключаются в круглые скобки. Нулевое количество входных параметры задается пустыми скобками:
Если лямбда-выражение имеет только один входной параметр, круглые скобки необязательны:
Два и более входных параметра разделяются запятыми:
Иногда компилятор не может вывести типы входных параметров. Вы можете указать типы данных в явном виде, как показано в следующем примере:
Для входных параметров все типы нужно задать либо в явном, либо в неявном виде. В противном случае компилятор выдает ошибку CS0748.
Начиная с C# 9.0, вы можете использовать пустые переменные, чтобы указать два или более входных параметра лямбда-выражения, которые не используются в выражении:
Параметры пустой переменной лямбда-выражения полезны, если вы используете лямбда-выражение для указания обработчика событий.
Если только один входной параметр имеет имя _ , для обеспечения обратной совместимости _ рассматривается как имя этого параметра в лямбда-выражении.
Асинхронные лямбда-выражения
С помощью ключевых слов async и await можно легко создавать лямбда-выражения и операторы, включающие асинхронную обработку. Например, в следующем примере Windows Forms содержится обработчик событий, который вызывает асинхронный метод ExampleMethodAsync и ожидает его.
Такой же обработчик событий можно добавить с помощью асинхронного лямбда-выражения. Чтобы добавить этот обработчик, поставьте модификатор async перед списком параметров лямбда-выражения, как показано в следующем примере:
Дополнительные сведения о создании и использовании асинхронных методов см. в разделе Асинхронное программирование с использованием ключевых слов Async и Await.
Лямбда-выражения и кортежи
В C# 7.0 представлена встроенная поддержка кортежей. Кортеж можно ввести в качестве аргумента лямбда-выражения, и лямбда-выражение также может возвращать кортеж. В некоторых случаях компилятор C# использует определение типа для определения типов компонентов кортежа.
Кортеж определяется путем заключения в скобки списка его компонентов с разделителями-запятыми. В следующем примере кортеж с тремя компонентами используется для передачи последовательности чисел в лямбда-выражение. Оно удваивает каждое значение и возвращает кортеж с тремя компонентами, содержащий результат операций умножения.
Как правило, поля кортежи именуются как Item1 , Item2 и т. д. Тем не менее кортеж с именованными компонентами можно определить, как показано в следующем примере:
Дополнительные сведения о кортежах в C# см. в статье Типы кортежей.
Лямбда-выражения со стандартными операторами запросов
В LINQ to Objects, наряду с другими реализациями, есть входной параметр, тип которого принадлежит к семейству универсальных делегатов Func<TResult>. Эти делегаты используют параметры типа для определения количества и типов входных параметров, а также тип возвращаемого значения делегата. Делегаты Func полезны для инкапсуляции пользовательских выражений, которые применяются к каждому элементу в наборе исходных данных. В качестве примера рассмотрим следующий тип делегата Func<T,TResult>:
Экземпляр этого делегата можно создать как Func<int, bool> , где int — входной параметр, а bool — возвращаемое значение. Возвращаемое значение всегда указывается в последнем параметре типа. Например, Func<int, string, bool> определяет делегат с двумя входными параметрами, int и string , и типом возвращаемого значения bool . Следующий делегат Func при вызове возвращает логическое значение, которое показывает, равен ли входной параметр 5:
Лямбда-выражения также можно использовать, когда аргумент имеет тип Expression<TDelegate>, например в стандартных операторах запросов, которые определены в типе Queryable. При указании аргумента Expression<TDelegate> лямбда-выражение компилируется в дерево выражения.
В этом примере используется стандартный оператор запроса Count:
Компилятор может вывести тип входного параметра ввода; но его также можно определить явным образом. Данное лямбда-выражение подсчитывает указанные целые значения ( n ), которые при делении на два дают остаток 1.
В следующем примере кода показано, как создать последовательность, которая содержит все элементы массива numbers , предшествующие 9, так как это первое число последовательности, не удовлетворяющее условию:
В следующем примере показано, как указать несколько входных параметров путем их заключения в скобки. Этот метод возвращает все элементы в массиве numbers до того числа, значение которого меньше его порядкового номера в массиве:
Лямбда-выражения не используются непосредственно в выражениях запросов, но их можно использовать в вызовах методов в выражениях запросов, как показано в следующем примере:
Определение типа в лямбда-выражениях
При написании лямбда-выражений обычно не требуется указывать тип входных параметров, так как компилятор может выводить этот тип на основе тела лямбда-выражения, типов параметров и других факторов, как описано в спецификации языка C#. Для большинства стандартных операторов запросов первой входное значение имеет тип элементов в исходной последовательности. При запросе IEnumerable<Customer> входная переменная считается объектом Customer , а это означает, что у вас есть доступ к его методам и свойствам:
Общие правила определения типа для лямбда-выражений формулируются следующим образом:
- лямбда-выражение должно содержать то же число параметров, что и тип делегата;
- каждый входной параметр в лямбда-выражении должен быть неявно преобразуемым в соответствующий параметр делегата;
- возвращаемое значение лямбда-выражения (если таковое имеется) должно быть неявно преобразуемым в возвращаемый тип делегата.
Естественный тип лямбда-выражения
Лямбда-выражение в самом себе не имеет типа, так как система общих типов не имеет встроенного понятия "лямбда-выражение". Однако иногда бывает удобно говорить о неформальном "типе" лямбда-выражения. Под неофициальным термином "тип" понимается тип делегата или тип Expression, в который преобразуется лямбда-выражение.
Начиная с C# 10, лямбда-выражение может иметь естественный тип. Вам не потребуется объявлять тип делегата, например Func<. > или Action<. > для лямбда-выражения, потому что компилятор может вывести тип делегата из лямбда-выражения. В качестве примера рассмотрим следующее объявление:
Компилятор может определить parse как Func<string, int> . Компилятор использует доступный делегат Func или Action , если он существует. Если нет, компилятор синтезирует тип делегата. Например, тип делегата синтезируется, если лямбда-выражение имеет параметры ref . Если лямбда-выражение имеет естественный тип, его можно присвоить менее явному типу, например System.Object или System.Delegate:
Группы методов (то есть имена методов без списков параметров) с ровно одной перегрузкой имеют естественный тип:
Если присвоить лямбда-выражение System.Linq.Expressions.LambdaExpression или System.Linq.Expressions.Expression, и лямбда имеет естественный тип делегата, выражение имеет естественный тип System.Linq.Expressions.Expression<TDelegate> с естественным типом делегата, используемым в качестве аргумента для параметра типа:
Не у всех лямбда-выражений есть естественный тип. Рассмотрим следующее объявление:
Компилятор не может определить тип параметра для s . Если компилятор не может определить естественный тип, необходимо объявить тип:
Явный тип возвращаемого значения
Как правило, тип возвращаемого значения лямбда-выражения является очевидным и легко выводится. Для некоторых выражений это не работает:
Начиная с C# 10, можно указать тип возвращаемого значения лямбда-выражения перед входными параметрами. Если вы указываете явный тип возвращаемого значения, заключите входные параметры в скобки:
Атрибуты
Начиная с C# 10, вы можете добавлять атрибуты в лямбда-выражение и его параметры. В следующем примере показано, как добавить атрибуты в лямбда-выражение:
Кроме того, вы можете добавить атрибуты во входные параметры или возвращаемое значение, как показано в следующем примере:
Как показано в предыдущих примерах, при добавлении атрибутов в лямбда-выражение или его параметры вам нужно заключить входные параметры в скобки.
Лямбда-выражения вызываются через базовый тип делегата. Это отличается от методов и локальных функций. Метод делегата Invoke не проверяет атрибуты в лямбда-выражении. При вызове лямбда-выражения атрибуты не оказывают никакого влияния. Атрибуты лямбда-выражений полезны для анализа кода и могут быть обнаружены с помощью отражения. Одно из последствий этого решения — невозможность применить System.Diagnostics.ConditionalAttribute к лямбда-выражению.
Запись внешних переменных и области видимости переменной в лямбда-выражениях
Лямбда-выражения могут ссылаться на внешние переменные. Это переменные в области метода, в котором определено лямбда-выражение, или области типа, который содержит лямбда-выражение. Переменные, полученные таким способом, сохраняются для использования в лямбда-выражениях, даже если бы в ином случае они оказались за границами области действия и уничтожились сборщиком мусора. Внешняя переменная должна быть определенным образом присвоена, прежде чем она сможет использоваться в лямбда-выражениях. В следующем примере демонстрируются эти правила.
Следующие правила применимы к области действия переменной в лямбда-выражениях.
- Захваченная переменная не будет уничтожена сборщиком мусора до тех пор, пока делегат, который на нее ссылается, не перейдет в статус подлежащего уничтожению при сборке мусора.
- Переменные, представленные в лямбда-выражении, невидимы в заключающем методе.
- Лямбда-выражение не может непосредственно захватывать параметры in, ref или out из заключающего метода.
- Оператор return в лямбда-выражении не вызывает возврат значения заключающим методом.
- Лямбда-выражение не может содержать операторы goto, break или continue, если целевой объект этого оператора перехода находится за пределами блока лямбда-выражения. Если целевой объект находится внутри блока, использование оператора перехода за пределами лямбда-выражения также будет ошибкой.
Начиная с C# 9.0, вы можете применять модификатор static к лямбда-выражению для предотвращения непреднамеренного сохранения локальных переменных или состояния экземпляров лямбда-выражением:
Статическое лямбда-выражение не может сохранять локальные переменные или состояние экземпляров из охватывающих областей, но может ссылаться на статические элементы и определения констант.
Спецификация языка C#
Дополнительные сведения о функциях, добавленных в C# 9.0 и более поздних версий, см. в следующих заметках о функциях: