Что такое лямбда выражение
Перейти к содержимому

Что такое лямбда выражение

  • автор:

Лямбда-выражения (Visual Basic)

Лямбда-выражение — это функция или подпрограмма без имени, которое можно использовать везде, где делегат является допустимым. Лямбда-выражения могут быть функциями или подпрограммами, а также однострочные или многострочные. Значения из текущего область можно передать лямбда-выражению.

Оператор RemoveHandler является исключением. Нельзя передать лямбда-выражение в для параметра делегата RemoveHandler .

Лямбда-выражения создаются с помощью Function ключевое слово или Sub так же, как и стандартная функция или подпрограмма. Однако лямбда-выражения включаются в оператор .

В следующем примере показано лямбда-выражение, которое увеличивает свой аргумент и возвращает значение . В примере показан синтаксис однострочного и многострочного лямбда-выражений для функции.

Dim increment1 = Function(x) x + 1 Dim increment2 = Function(x) Return x + 2 End Function ' Write the value 2. Console.WriteLine(increment1(1)) ' Write the value 4. Console.WriteLine(increment2(2)) 

В следующем примере показано лямбда-выражение, записывающее значение в консоль. В примере показан синтаксис однострочного и многострочного лямбда-выражений для подпрограммы.

Dim writeline1 = Sub(x) Console.WriteLine(x) Dim writeline2 = Sub(x) Console.WriteLine(x) End Sub ' Write "Hello". writeline1("Hello") ' Write "World" writeline2("World") 

Обратите внимание, что в предыдущих примерах лямбда-выражения присваиваются имени переменной. Всякий раз, когда вы ссылаетесь на переменную, вызывается лямбда-выражение. Вы также можете одновременно объявить и вызвать лямбда-выражение, как показано в следующем примере.

Console.WriteLine((Function(num As Integer) num + 1)(5)) 

Лямбда-выражение может быть возвращено в качестве значения вызова функции (как показано в примере в разделе Контекст далее в этом разделе) или передано в качестве аргумента параметру, принимающему тип делегата, как показано в следующем примере.

Module Module2 Sub Main() ' The following line will print Success, because 4 is even. testResult(4, Function(num) num Mod 2 = 0) ' The following line will print Failure, because 5 is not > 10. testResult(5, Function(num) num > 10) End Sub ' Sub testResult takes two arguments, an integer value and a ' delegate function that takes an integer as input and returns ' a boolean. ' If the function returns True for the integer argument, Success ' is displayed. ' If the function returns False for the integer argument, Failure ' is displayed. Sub testResult(ByVal value As Integer, ByVal fun As Func(Of Integer, Boolean)) If fun(value) Then Console.WriteLine("Success") Else Console.WriteLine("Failure") End If End Sub End Module 

Синтаксис лямбда-выражений

Синтаксис лямбда-выражения похож на синтаксис стандартной функции или подпрограммы. Различия заключаются в следующем.

  • Лямбда-выражение не имеет имени.
  • Лямбда-выражения не могут иметь модификаторы, такие как Overloads или Overrides .
  • Однострочные лямбда-функции не используют As предложение для обозначения возвращаемого типа. Вместо этого тип выводится из значения, которое вычисляется телом лямбда-выражения. Например, если тело лямбда-выражения имеет значение cust.City = «London» , его тип возвращаемого значения — Boolean .
  • В многостроковых лямбда-функциях можно либо указать тип возвращаемого As значения с помощью предложения , либо опустить As предложение, чтобы вывести тип возвращаемого значения. As Если предложение опущено для многострочной лямбда-функции, возвращаемый тип определяется как доминирующий тип из всех Return операторов в многострочной лямбда-функции. Доминирующий тип — это уникальный тип, до который могут быть расширены все остальные типы. Если этот уникальный тип не может быть определен, доминирующим типом является уникальный тип, к которому могут сузиться все остальные типы в массиве. Если ни один из указанных уникальных типов нельзя определить, главным типом будет Object . В этом случае, если Option Strict задано значение On , возникает ошибка компилятора. Например, если выражения, предоставленные инструкции Return , содержат значения типа Integer , Long и Double , результирующий массив имеет тип Double . Как, так Integer и расширение до Double и только Double Long . Поэтому Double является главным типом. Для получения дополнительной информации см. Widening and Narrowing Conversions.
  • Тело однострочной функции должно быть выражением, возвращающим значение, а не оператором . Оператор для однострочных функций отсутствует Return . Значение, возвращаемое однострочной функцией, является значением выражения в теле функции.
  • Текст однострочного подпрограммы должен быть однострочный оператор.
  • Однострочные функции и подпрограммы не включают End Function оператор или End Sub .
  • Вы можете указать тип данных параметра лямбда-выражения с помощью As ключевое слово или вывести тип данных параметра. Либо все параметры должны иметь указанные типы данных, либо все должны быть выведены.
  • Optional Параметры и Paramarray не разрешены.
  • Универсальные параметры не допускаются.

Асинхронные лямбда-выражения

Вы можете легко создавать лямбда-выражения и инструкции, включающие асинхронную обработку, с помощью ключевых слов операторов Async и Await . Например, в следующем примере Windows Forms содержится обработчик событий, который вызывает асинхронный метод ExampleMethodAsync и ожидает его.

Public Class Form1 Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click ' ExampleMethodAsync returns a Task. Await ExampleMethodAsync() TextBox1.Text = vbCrLf & "Control returned to button1_Click." End Sub Async Function ExampleMethodAsync() As Task ' The following line simulates a task-returning asynchronous process. Await Task.Delay(1000) End Function End Class 

Этот же обработчик событий можно добавить с помощью асинхронного лямбда-выражения в инструкции AddHandler. Чтобы добавить этот обработчик, поставьте модификатор Async перед списком параметров лямбда-выражения, как показано в следующем примере.

Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load AddHandler Button1.Click, Async Sub(sender1, e1) ' ExampleMethodAsync returns a Task. Await ExampleMethodAsync() TextBox1.Text = vbCrLf & "Control returned to Button1_ Click." End Sub End Sub Async Function ExampleMethodAsync() As Task ' The following line simulates a task-returning asynchronous process. Await Task.Delay(1000) End Function End Class 

Дополнительные сведения о создании и использовании асинхронных методов см. в разделах Асинхронное программирование с помощью Async и Await.

Контекст

Лямбда-выражение использует свой контекст с область, в котором оно определено. Он имеет те же права доступа, что и любой код, написанный на область. Сюда входит доступ к переменным-членам, функциям и дочерним элементам, Me а также к параметрам и локальным переменным в содержающих область.

Доступ к локальным переменным и параметрам в содержащей область может превышать время существования этого область. Пока делегат, ссылающийся на лямбда-выражение, недоступен для сборки мусора, доступ к переменным в исходной среде сохраняется. В следующем примере переменная target является локальной для makeTheGame метода , в котором определено лямбда-выражение playTheGame . Обратите внимание, что возвращенное лямбда-выражение, назначенное takeAGuess в Main , по-прежнему имеет доступ к локальной переменной target .

Module Module6 Sub Main() ' Variable takeAGuess is a Boolean function. It stores the target ' number that is set in makeTheGame. Dim takeAGuess As gameDelegate = makeTheGame() ' Set up the loop to play the game. Dim guess As Integer Dim gameOver = False While Not gameOver guess = CInt(InputBox("Enter a number between 1 and 10 (0 to quit)", "Guessing Game", "0")) ' A guess of 0 means you want to give up. If guess = 0 Then gameOver = True Else ' Tests your guess and announces whether you are correct. Method takeAGuess ' is called multiple times with different guesses. The target value is not ' accessible from Main and is not passed in. gameOver = takeAGuess(guess) Console.WriteLine("Guess of " & guess & " is " & gameOver) End If End While End Sub Delegate Function gameDelegate(ByVal aGuess As Integer) As Boolean Public Function makeTheGame() As gameDelegate ' Generate the target number, between 1 and 10. Notice that ' target is a local variable. After you return from makeTheGame, ' it is not directly accessible. Randomize() Dim target As Integer = CInt(Int(10 * Rnd() + 1)) ' Print the answer if you want to be sure the game is not cheating ' by changing the target at each guess. Console.WriteLine("(Peeking at the answer) The target is " & target) ' The game is returned as a lambda expression. The lambda expression ' carries with it the environment in which it was created. This ' environment includes the target number. Note that only the current ' guess is a parameter to the returned lambda expression, not the target. ' Does the guess equal the target? Dim playTheGame = Function(guess As Integer) guess = target Return playTheGame End Function End Module 

В следующем примере демонстрируется широкий спектр прав доступа для вложенного лямбда-выражения. Когда возвращаемое лямбда-выражение выполняется из Main , aDel оно обращается к следующим элементам:

  • Поле класса, в котором он определен: aField
  • Свойство класса, в котором он определен: aProp
  • Параметр метода functionWithNestedLambda , в котором он определен: level1
  • Локальная переменная : functionWithNestedLambda localVar
  • Параметр лямбда-выражения, в котором оно вложено: level2
Module Module3 Sub Main() ' Create an instance of the class, with 1 as the value of ' the property. Dim lambdaScopeDemoInstance = New LambdaScopeDemoClass With ' Variable aDel will be bound to the nested lambda expression ' returned by the call to functionWithNestedLambda. ' The value 2 is sent in for parameter level1. Dim aDel As aDelegate = lambdaScopeDemoInstance.functionWithNestedLambda(2) ' Now the returned lambda expression is called, with 4 as the ' value of parameter level3. Console.WriteLine("First value returned by aDel: " & aDel(4)) ' Change a few values to verify that the lambda expression has ' access to the variables, not just their original values. lambdaScopeDemoInstance.aField = 20 lambdaScopeDemoInstance.Prop = 30 Console.WriteLine("Second value returned by aDel: " & aDel(40)) End Sub Delegate Function aDelegate( ByVal delParameter As Integer) As Integer Public Class LambdaScopeDemoClass Public aField As Integer = 6 Dim aProp As Integer Property Prop() As Integer Get Return aProp End Get Set(ByVal value As Integer) aProp = value End Set End Property Public Function functionWithNestedLambda( ByVal level1 As Integer) As aDelegate Dim localVar As Integer = 5 ' When the nested lambda expression is executed the first ' time, as aDel from Main, the variables have these values: ' level1 = 2 ' level2 = 3, after aLambda is called in the Return statement ' level3 = 4, after aDel is called in Main ' localVar = 5 ' aField = 6 ' aProp = 1 ' The second time it is executed, two values have changed: ' aField = 20 ' aProp = 30 ' level3 = 40 Dim aLambda = Function(level2 As Integer) _ Function(level3 As Integer) _ level1 + level2 + level3 + localVar + aField + aProp ' The function returns the nested lambda, with 3 as the ' value of parameter level2. Return aLambda(3) End Function End Class End Module 

Преобразование в тип делегата

Лямбда-выражение можно неявно преобразовать в совместимый тип делегата. Сведения об общих требованиях к совместимости см. в разделе Расслабленное преобразование делегатов. Например, в следующем примере кода показано лямбда-выражение, которое неявно преобразуется в Func(Of Integer, Boolean) или соответствующую сигнатуру делегата.

' Explicitly specify a delegate type. Delegate Function MultipleOfTen(ByVal num As Integer) As Boolean ' This function matches the delegate type. Function IsMultipleOfTen(ByVal num As Integer) As Boolean Return num Mod 10 = 0 End Function ' This method takes an input parameter of the delegate type. ' The checkDelegate parameter could also be of ' type Func(Of Integer, Boolean). Sub CheckForMultipleOfTen(ByVal values As Integer(), ByRef checkDelegate As MultipleOfTen) For Each value In values If checkDelegate(value) Then Console.WriteLine(value & " is a multiple of ten.") Else Console.WriteLine(value & " is not a multiple of ten.") End If Next End Sub ' This method shows both an explicitly defined delegate and a ' lambda expression passed to the same input parameter. Sub CheckValues() Dim values = CheckForMultipleOfTen(values, AddressOf IsMultipleOfTen) CheckForMultipleOfTen(values, Function(num) num Mod 10 = 0) End Sub 

В следующем примере кода показано лямбда-выражение, которое неявно преобразуется в Sub(Of Double, String, Double) или соответствующую сигнатуру делегата.

Module Module1 Delegate Sub StoreCalculation(ByVal value As Double, ByVal calcType As String, ByVal result As Double) Sub Main() ' Create a DataTable to store the data. Dim valuesTable = New DataTable("Calculations") valuesTable.Columns.Add("Value", GetType(Double)) valuesTable.Columns.Add("Calculation", GetType(String)) valuesTable.Columns.Add("Result", GetType(Double)) ' Define a lambda subroutine to write to the DataTable. Dim writeToValuesTable = Sub(value As Double, calcType As String, result As Double) Dim row = valuesTable.NewRow() row(0) = value row(1) = calcType row(2) = result valuesTable.Rows.Add(row) End Sub ' Define the source values. Dim s = ' Perform the calculations. Array.ForEach(s, Sub(c) CalculateSquare(c, writeToValuesTable)) Array.ForEach(s, Sub(c) CalculateSquareRoot(c, writeToValuesTable)) ' Display the data. Console.WriteLine("Value" & vbTab & "Calculation" & vbTab & "Result") For Each row As DataRow In valuesTable.Rows Console.WriteLine(row(0).ToString() & vbTab & row(1).ToString() & vbTab & row(2).ToString()) Next End Sub Sub CalculateSquare(ByVal number As Double, ByVal writeTo As StoreCalculation) writeTo(number, "Square ", number ^ 2) End Sub Sub CalculateSquareRoot(ByVal number As Double, ByVal writeTo As StoreCalculation) writeTo(number, "Square Root", Math.Sqrt(number)) End Sub End Module 

При назначении лямбда-выражений делегатам или их передаче в качестве аргументов в процедуры можно указать имена параметров, но опустить их типы данных, позволяя принимать типы из делегата.

Примеры

  • В следующем примере определяется лямбда-выражение, которое возвращает True значение , если аргументу типа значения, допускающего значение NULL, присвоено значение, и False если его значение равно Nothing .
Dim notNothing = Function(num? As Integer) num IsNot Nothing Dim arg As Integer = 14 Console.WriteLine("Does the argument have an assigned value?") Console.WriteLine(notNothing(arg)) 
Dim numbers() = Dim lastIndex = Function(intArray() As Integer) intArray.Length - 1 For i = 0 To lastIndex(numbers) numbers(i) += 1 Next 

См. также раздел

  • Процедуры
  • Introduction to LINQ in Visual Basic (Знакомство с LINQ в Visual Basic)
  • Делегаты
  • Оператор Function
  • Оператор Sub
  • Типы значений, допускающие значение NULL
  • Практическое руководство. Передача процедур другой процедуре в Visual Basic
  • Практическое руководство. Создание лямбда-выражения
  • Неявное преобразование делегата

Совместная работа с нами на GitHub

Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

Что такое лямбда выражение

Лямбда-выражения представляют упрощенную запись анонимных методов. Лямбда-выражения позволяют создать емкие лаконичные методы, которые могут возвращать некоторое значение и которые можно передать в качестве параметров в другие методы.

Ламбда-выражения имеют следующий синтаксис: слева от лямбда-оператора => определяется список параметров, а справа блок выражений, использующий эти параметры:

(список_параметров) => выражение

С точки зрения типа данных лямбда-выражение представляет делегат. Например, определим простейшее лямбда-выражение:

Message hello = () => Console.WriteLine("Hello"); hello(); // Hello hello(); // Hello hello(); // Hello delegate void Message();

В данном случае переменная hello представляет делегат Message — то есть некоторое действие, которое ничего не возвращает и не принимает никаких параметров. В качестве значения этой переменной присваивается лямбда-выражение. Это лямбда-выражение должно соответствовать делегату Message — оно тоже не принимает никаких параметров, поэтому слева от лямбда-оператора идут пустые скобки. А справа от лямбда-оператора идет выполняемое выражение — Console.WriteLine(«Hello»)

Затем в программе можно вызывать эту переменную как метод.

Если лямбда-выражение содержит несколько действий, то они помещаются в фигурные скобки:

Message hello = () => < Console.Write("Hello "); Console.WriteLine("World"); >; hello(); // Hello World

Выше мы определили переменную hello, которая представляет делегат Message. Но начиная с версии C# 10 мы можем применять неявную типизацию (определение переменной с помощью оператора var ) при определении лямбда-выражения:

var hello = () => Console.WriteLine("Hello"); hello(); // Hello hello(); // Hello hello(); // Hello

Но какой тип в данном случае представляет переменная hello? При неявной типизации компилятор сам пытается сопоставить лямбда-выражение на основе его опеределения с каким-нибудь делегатом. Например, выше определенное лямбда-выражение hello по умолчанию компилятор будет рассматривать как переменную встроенного делегата Action , который не принимает никаких параметров и ничего не возвращает.

Параметры лямбды

При определении списка параметров мы можем не указывать для них тип данных:

Operation sum = (x, y) => Console.WriteLine($" + = "); sum(1, 2); // 1 + 2 = 3 sum(22, 14); // 22 + 14 = 36 delegate void Operation(int x, int y);

В данном случае компилятор видит, что лямбда-выражение sum представляет тип Operation, а значит оба параметра лямбды представляют тип int . Поэтому никак проблем не возникнет.

Однако если мы применяем неявную типизацию, то у компилятора могут возникнуть трудности, чтобы вывести тип делегата для лямбда-выражения, например, в следующем случае

var sum = (x, y) => Console.WriteLine($" + = "); // ! Ошибка

В этом случае можно указать тип параметров

var sum = (int x, int y) => Console.WriteLine($" + = "); sum(1, 2); // 1 + 2 = 3 sum(22, 14); // 22 + 14 = 36

Если лямбда имеет один параметр, для которого не требуется указывать тип данных, то скобки можно опустить:

PrintHandler print = message => Console.WriteLine(message); print("Hello"); // Hello print("Welcome"); // Welcome delegate void PrintHandler(string message);

Начиная с C# 12 параметры лямбда-выражений могут иметь значения по умолчанию:

var welcome = (string message = "hello")=> Console.WriteLine(message); welcome("hello world"); // hello world welcome(); // hello

Возвращение результата

Лямбда-выражение может возвращать результат. Возвращаемый результат можно указать после лямбда-оператора:

var sum = (int x, int y) => x + y; int sumResult = sum(4, 5); // 9 Console.WriteLine(sumResult); // 9 Operation multiply = (x, y) => x * y; int multiplyResult = multiply(4, 5); // 20 Console.WriteLine(multiplyResult); // 20 delegate int Operation(int x, int y);

Если лямбда-выражение содержит несколько выражений (или одно выражение в фигурных скобках), тогда нужно использовать оператор return , как в обычных методах:

var subtract = (int x, int y) => < if (x >y) return x - y; else return y - x; >; int result1 = subtract(10, 6); // 4 Console.WriteLine(result1); // 4 int result2 = subtract(-10, 6); // 16 Console.WriteLine(result2); // 16

Добавление и удаление действий в лямбда-выражении

Поскольку лямбда-выражение представляет делегат, тот как и в делегат, в переменную, которая представляет лямбда-выражение можно добавлять методы и другие лямбды:

var hello = () => Console.WriteLine("METANIT.COM"); var message = () => Console.Write("Hello "); message += () => Console.WriteLine("World"); // добавляем анонимное лямбда-выражение message += hello; // добавляем лямбда-выражение из переменной hello message += Print; // добавляем метод message(); Console.WriteLine("--------------"); // для разделения вывода message -= Print; // удаляем метод message -= hello; // удаляем лямбда-выражение из переменной hello message?.Invoke(); // на случай, если в message больше нет действий void Print() => Console.WriteLine("Welcome to C#");
Hello World METANIT.COM Welcome to C# -------------- Hello World

Лямбда-выражение как аргумент метода

Как и делегаты, лямбда-выражения можно передавать параметрам метода, которые представляют делегат:

int[] integers = < 1, 2, 3, 4, 5, 6, 7, 8, 9 >; // найдем сумму чисел больше 5 int result1 = Sum(integers, x => x > 5); Console.WriteLine(result1); // 30 // найдем сумму четных чисел int result2 = Sum(integers, x => x % 2 == 0); Console.WriteLine(result2); //20 int Sum(int[] numbers, IsEqual func) < int result = 0; foreach (int i in numbers) < if (func(i)) result += i; >return result; > delegate bool IsEqual(int x);

Метод Sum принимает в качестве параметра массив чисел и делегат IsEqual и возвращает сумму чисел массива в виде объекта int. В цикле проходим по всем числам и складываем их. Причем складываем только те числа, для которых делегат IsEqual func возвращает true. То есть делегат IsEqual здесь фактически задает условие, которому должны соответствовать значения массива. Но на момент написания метода Sum нам неизвестно, что это за условие.

При вызове метода Sum ему передается массив и лямбда-выражение:

int result1 = Sum(integers, x => x > 5);

То есть параметр x здесь будет представлять число, которое передается в делегат:

if (func(i))

А выражение x > 5 представляет условие, которому должно соответствовать число. Если число соответствует этому условию, то лямбда-выражение возвращает true, а переданное число складывается с другими числами.

Подобным образом работает второй вызов метода Sum, только здесь уже идет проверка числа на четность, то есть если остаток от деления на 2 равен нулю:

int result2 = Sum(integers, x => x % 2 == 0);

Лямбда-выражение как результат метода

Метод также может возвращать лямбда-выражение. В этом случае возвращаемым типом метода выступает делегат, которому соответствует возвращаемое лямбда-выражение. Например:

Operation operation = SelectOperation(OperationType.Add); Console.WriteLine(operation(10, 4)); // 14 operation = SelectOperation(OperationType.Subtract); Console.WriteLine(operation(10, 4)); // 6 operation = SelectOperation(OperationType.Multiply); Console.WriteLine(operation(10, 4)); // 40 Operation SelectOperation(OperationType opType) < switch (opType) < case OperationType.Add: return (x, y) =>x + y; case OperationType.Subtract: return (x, y) => x - y; default: return (x, y) => x * y; > > enum OperationType < Add, Subtract, Multiply >delegate int Operation(int x, int y);

В данном случае метод SelectOperation() в качестве параметра принимает перечисление типа OperationType. Это перечисление хранит три константы, каждая из которых соответствует определенной арифметической операции. И в самом методе в зависимости от значения параметра возвращаем определенное лямбда-выражение.

Лямбда-выражения

Начиная с C# 3.0, доступен новый синтаксис для назначения реализации кода делегатам, называемый . Лямбда-выражения могут использоваться везде, где есть параметр типа делегата.

Синтаксис лямбда-выражений проще синтаксиса анонимных методов. В случае если подлежащий вызову метод имеет параметры, а эти параметры не нужны, синтаксис анонимных методов проще, поскольку в этом случае указывать параметры не потребуется.

Во всех лямбда-выражениях применяется новый лямбда-оператор =>, который разделяет лямбда-выражение на две части. В левой его части указывается входной параметр (или несколько параметров), а в правой части — тело лямбда-выражения. Оператор => иногда описывается такими словами, как «переходит» или «становится».

В C# поддерживаются две разновидности лямбда-выражений в зависимости от тела самого лямбда-выражения. Так, если тело лямбда-выражения состоит из одного выражения, то образуется одиночное лямбда-выражение. В этом случае тело выражения не заключается в фигурные скобки. Если же тело лямбда-выражения состоит из блока операторов, заключенных в фигурные скобки, то образуется блочное лямбда-выражение. При этом блочное лямбда-выражение может содержать целый ряд операторов, в том числе циклы, вызовы методов и условные операторы if. Обе разновидности лямбда-выражений рассматриваются далее по отдельности.

Одиночные лямбда-выражения

В одиночном лямбда-выражении часть, находящаяся справа от оператора =>, воздействует на параметр (или ряд параметров), указываемый слева. Возвращаемым результатом вычисления такого выражения является результат выполнения лямбда-оператора. Ниже приведена общая форма одиночного лямбда-выражения, принимающего единственный параметр:

параметр => выражение

Если же требуется указать несколько параметров, то используется следующая форма:

(список_параметров) => выражение

Таким образом, когда требуется указать два параметра или более, их следует заключить в скобки. Если же выражение не требует параметров, то следует использовать пустые скобки.

Лямбда-выражение применяется в два этапа. Сначала объявляется тип делегата, совместимый с лямбда-выражением, а затем экземпляр делегата, которому присваивается лямбда-выражение. После этого лямбда-выражение вычисляется при обращении к экземпляру делегата. Результатом его вычисления становится возвращаемое значение. Давайте рассмотрим пример:

using System; namespace ConsoleApplication1 < // Создадим несколько делегатов имитирующих // простейшую форму регистрации delegate int LengthLogin(string s); delegate bool BoolPassword(string s1, string s2); class Program < private static void SetLogin() < Console.Write("Введите логин: "); string login = Console.ReadLine(); // Используем лямбда-выражение LengthLogin lengthLoginDelegate = s =>s.Length; int lengthLogin = lengthLoginDelegate(login); if (lengthLogin > 25) < Console.WriteLine("Слишком длинное имя\n"); // Рекурсия на этот же метод, чтобы ввести заново логин SetLogin(); >> static void Main() < SetLogin(); Console.Write("Введите пароль: "); string password1 = Console.ReadLine(); Console.Write("Повторите пароль: "); string password2 = Console.ReadLine(); // Используем лямбда выражение BoolPassword bp = (s1, s2) =>s1 == s2; if (bp(password1, password2)) Console.WriteLine("Регистрация удалась!"); else Console.WriteLine("Регистрация провалилась. Пароли не совпадают"); Console.ReadLine(); > > > 

Использование одиночных лямбда-выражений

Блочные лямбда-выражения

Второй разновидностью лямбда-выражений является блочное лямбда-выражение. Для такого лямбда-выражения характерны расширенные возможности выполнения различных операций, поскольку в его теле допускается указывать несколько операторов. Например, в блочном лямбда-выражении можно использовать циклы и условные операторы if, объявлять переменные и т.д. Создать блочное лямбда-выражение нетрудно. Для этого достаточно заключить тело выражения в фигурные скобки. Помимо возможности использовать несколько операторов, в остальном блочное лямбда-выражение, практически ничем не отличается от только что рассмотренного одиночного лямбда-выражения.

Давайте модифицируем предыдущий пример, добавив капчу в форму регистрации:

using System; namespace ConsoleApplication1 < // Создадим несколько делегатов имитирующих // простейшую форму регистрации delegate int LengthLogin(string s); delegate bool BoolPassword(string s1, string s2); delegate void Captha(string s1, string s2); class Program < private static void SetLogin() < Console.Write("Введите логин: "); string login = Console.ReadLine(); // Используем лямбда-выражение LengthLogin lengthLoginDelegate = s =>s.Length; int lengthLogin = lengthLoginDelegate(login); if (lengthLogin > 25) < Console.WriteLine("Слишком длинное имя\n"); // Рекурсия на этот же метод, чтобы ввести заново логин SetLogin(); >> static void Main() < SetLogin(); Console.Write("Введите пароль: "); string password1 = Console.ReadLine(); Console.Write("Повторите пароль: "); string password2 = Console.ReadLine(); // Используем лямбда выражение BoolPassword bp = (s1, s2) =>s1 == s2; if (bp(password1, password2)) < Random ran = new Random(); string resCaptha = ""; for (int i = 0; i < 10; i++) resCaptha += (char)ran.Next(0, 100); Console.WriteLine("Введите код xD: " + resCaptha); string resCode = Console.ReadLine(); // Реализуем блочное лямбда-выражение Captha cp = (s1, s2) =>< if (s1 == s2) Console.WriteLine("Регистрация удалась!"); else Console.WriteLine("Не переживай, в следующий раз получится :)"); return; >; cp(resCaptha, resCode); > else Console.WriteLine("Регистрация провалилась. Пароли не совпадают"); Console.ReadLine(); > > >

Лямбда-выражения и анонимные функции

Лямбда-выражение используется для создания анонимной функции. Используйте оператор объявления лямбда-выражения => для отделения списка параметров лямбда-выражения от исполняемого кода. Лямбда-выражение может иметь одну из двух следующих форм:

    Лямбда выражения, имеющая выражение в качестве текста:

(input-parameters) => expression 
(input-parameters) => < > 

Чтобы создать лямбда-выражение, необходимо указать входные параметры (если они есть) с левой стороны лямбда-оператора и блок выражений или операторов с другой стороны.

Лямбда-выражение может быть преобразовано в тип делегата. Тип делегата, в который может быть преобразовано лямбда-выражение, определяется типами его параметров и возвращаемым значением. Если лямбда-выражение не возвращает значение, оно может быть преобразовано в один из типов делегата Action ; в противном случае его можно преобразовать в один из типов делегатов Func . Например, лямбда-выражение, которое имеет два параметра и не возвращает значение, можно преобразовать в делегат Action . Лямбда-выражение, которое имеет два параметра и возвращает значение, можно преобразовать в делегат Func . В следующем примере лямбда-выражение x => x * x , которое указывает параметр с именем x и возвращает значение x в квадрате, присваивается переменной типа делегата:

Func square = x => x * x; Console.WriteLine(square(5)); // Output: // 25 

Лямбда-выражения можно также преобразовать в типы дерева выражения, как показано в следующем примере:

System.Linq.Expressions.Expression> e = x => x * x; Console.WriteLine(e); // Output: // x => (x * x) 

Лямбда-выражения можно использовать в любом коде, для которого требуются экземпляры типов делегатов или деревьев выражений, например в качестве аргумента метода Task.Run(Action) для передачи кода, который должен выполняться в фоновом режиме. Можно также использовать лямбда-выражения при применении LINQ в C#, как показано в следующем примере:

int[] numbers = < 2, 3, 4, 5 >; var squaredNumbers = numbers.Select(x => x * x); Console.WriteLine(string.Join(" ", squaredNumbers)); // Output: // 4 9 16 25 

При использовании синтаксиса на основе методов для вызова метода Enumerable.Select в классе System.Linq.Enumerable (например, в LINQ to Objects и LINQ to XML) параметром является тип делегата System.Func . При вызове метода Queryable.Select в классе System.Linq.Queryable (например, в LINQ to SQL) типом параметра является тип дерева выражения Expression> . В обоих случаях можно использовать одно и то же лямбда-выражение для указания значения параметра. Поэтому оба вызова Select выглядят одинаково, хотя на самом деле объект, созданный из лямбда-выражения, имеет другой тип.

Выражения-лямбды

Лямбда-выражение с выражением с правой стороны оператора => называется выражением лямбда. Выражения-лямбды возвращают результат выражения и принимают следующую основную форму.

(input-parameters) => expression 

Текст выражения лямбды может состоять из вызова метода. Но при создании деревьев выражений, которые вычисляются вне контекста поддержки общеязыковой среды выполнения (CRL) .NET, например в SQL Server, вызовы методов не следует использовать в лямбда-выражениях. Методы не имеют смысла вне контекста среды CLR .NET.

Лямбды операторов

Лямбда-инструкция напоминает лямбда-выражение, за исключением того, что инструкции заключаются в фигурные скобки:

(input-parameters) => < > 

Тело лямбды оператора может состоять из любого количества операторов; однако на практике обычно используется не более двух-трех.

Action greet = name => < string greeting = $"Hello !"; Console.WriteLine(greeting); >; greet("World"); // Output: // Hello World! 

Лямбда-инструкции нельзя использовать для создания деревьев выражений.

Входные параметры лямбда-выражения

Входные параметры лямбда-выражения заключаются в круглые скобки. Нулевое количество входных параметров задается пустыми скобками:

Action line = () => Console.WriteLine(); 

Если лямбда-выражение имеет только один входной параметр, круглые скобки необязательны:

Func cube = x => x * x * x; 

Два и более входных параметра разделяются запятыми:

Func testForEquality = (x, y) => x == y; 

Иногда компилятор не может вывести типы входных параметров. Вы можете указать типы данных в явном виде, как показано в следующем примере:

Func isTooLong = (int x, string s) => s.Length > x; 

Для входных параметров все типы нужно задать либо в явном, либо в неявном виде. В противном случае компилятор выдает ошибку CS0748.

Вы можете использовать dis карта s для указания двух или нескольких входных параметров лямбда-выражения, которые не используются в выражении:

Func constant = (_, _) => 42; 

Параметры пустой переменной лямбда-выражения полезны, если вы используете лямбда-выражение для указания обработчика событий.

Если только один входной параметр имеет имя _ , для обеспечения обратной совместимости _ рассматривается как имя этого параметра в лямбда-выражении.

Начиная с C# 12, можно указать значения по умолчанию для параметров в лямбда-выражениях. Синтаксис и ограничения значений параметров по умолчанию совпадают с методами и локальными функциями. В следующем примере объявляется лямбда-выражение с параметром по умолчанию, а затем вызывает его один раз с использованием значения по умолчанию и один раз с двумя явными параметрами:

var IncrementBy = (int source, int increment = 1) => source + increment; Console.WriteLine(IncrementBy(5)); // 6 Console.WriteLine(IncrementBy(5, 2)); // 7 

Можно также объявить лямбда-выражения с массивами в params качестве параметров:

var sum = (params int[] values) => < int sum = 0; foreach (var value in values) sum += value; return sum; >; var empty = sum(); Console.WriteLine(empty); // 0 var sequence = new[] < 1, 2, 3, 4, 5 >; var total = sum(sequence); Console.WriteLine(total); // 15 

В рамках этих обновлений, когда группе методов, которая имеет параметр по умолчанию, назначается лямбда-выражение, это лямбда-выражение также имеет тот же параметр по умолчанию. Группу методов с параметром массива params также можно назначить лямбда-выражению.

Лямбда-выражения с параметрами или params массивами по умолчанию в качестве параметров не имеют естественных типов, соответствующих Func<> или Action<> типам. Однако можно определить типы делегатов, которые включают значения параметров по умолчанию:

delegate int IncrementByDelegate(int source, int increment = 1); delegate int SumDelegate(params int[] values); 

Кроме того, можно использовать неявно типизированные переменные с var объявлениями для определения типа делегата. Компилятор синтезирует правильный тип делегата.

Дополнительные сведения см. в спецификации компонентов для параметров по умолчанию для лямбда-выражений.

Асинхронные лямбда-выражения

С помощью ключевых слов async и await можно легко создавать лямбда-выражения и операторы, включающие асинхронную обработку. Например, в следующем примере Windows Forms содержится обработчик событий, который вызывает асинхронный метод ExampleMethodAsync и ожидает его.

public partial class Form1 : Form < public Form1() < InitializeComponent(); button1.Click += button1_Click; >private async void button1_Click(object sender, EventArgs e) < await ExampleMethodAsync(); textBox1.Text += "\r\nControl returned to Click event handler.\n"; >private async Task ExampleMethodAsync() < // The following line simulates a task-returning asynchronous process. await Task.Delay(1000); >> 

Такой же обработчик событий можно добавить с помощью асинхронного лямбда-выражения. Чтобы добавить этот обработчик, поставьте модификатор async перед списком параметров лямбда-выражения, как показано в следующем примере:

public partial class Form1 : Form < public Form1() < InitializeComponent(); button1.Click += async (sender, e) =>< await ExampleMethodAsync(); textBox1.Text += "\r\nControl returned to Click event handler.\n"; >; > private async Task ExampleMethodAsync() < // The following line simulates a task-returning asynchronous process. await Task.Delay(1000); >> 

Дополнительные сведения о создании и использовании асинхронных методов см. в разделе Асинхронное программирование с использованием ключевых слов Async и Await.

Лямбда-выражения и кортежи

Язык C# обеспечивает встроенную поддержку кортежей. Кортеж можно ввести в качестве аргумента лямбда-выражения, и лямбда-выражение также может возвращать кортеж. В некоторых случаях компилятор C# использует определение типа для определения типов компонентов кортежа.

Кортеж определяется путем заключения в скобки списка его компонентов с разделителями-запятыми. В следующем примере кортеж с тремя компонентами используется для передачи последовательности чисел в лямбда-выражение. Оно удваивает каждое значение и возвращает кортеж с тремя компонентами, содержащий результат операций умножения.

Func <(int, int, int), (int, int, int)>doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3); var numbers = (2, 3, 4); var doubledNumbers = doubleThem(numbers); Console.WriteLine($"The set doubled: "); // Output: // The set (2, 3, 4) doubled: (4, 6, 8) 

Как правило, поля кортежи именуются как Item1 , Item2 и т. д. Тем не менее кортеж с именованными компонентами можно определить, как показано в следующем примере:

Func <(int n1, int n2, int n3), (int, int, int)>doubleThem = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3); var numbers = (2, 3, 4); var doubledNumbers = doubleThem(numbers); Console.WriteLine($"The set doubled: "); 

Дополнительные сведения о кортежах в C# см. в статье Типы кортежей.

Лямбда-выражения со стандартными операторами запросов

public delegate TResult Func(T arg) 

Экземпляр этого делегата можно создать как Func , где int — входной параметр, а bool — возвращаемое значение. Возвращаемое значение всегда указывается в последнем параметре типа. Например, Func определяет делегат с двумя входными параметрами, int и string , и типом возвращаемого значения bool . Следующий делегат Func при вызове возвращает логическое значение, которое показывает, равен ли входной параметр 5:

Func equalsFive = x => x == 5; bool result = equalsFive(4); Console.WriteLine(result); // False 

В этом примере используется стандартный оператор запроса Count:

int[] numbers = < 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 >; int oddNumbers = numbers.Count(n => n % 2 == 1); Console.WriteLine($"There are odd numbers in "); 

Компилятор может вывести тип входного параметра ввода; но его также можно определить явным образом. Данное лямбда-выражение подсчитывает указанные целые значения ( n ), которые при делении на два дают остаток 1.

В следующем примере кода показано, как создать последовательность, которая содержит все элементы массива numbers , предшествующие 9, так как это первое число последовательности, не удовлетворяющее условию:

int[] numbers = < 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 >; var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6); Console.WriteLine(string.Join(" ", firstNumbersLessThanSix)); // Output: // 5 4 1 3 

В следующем примере показано, как указать несколько входных параметров путем их заключения в скобки. Этот метод возвращает все элементы в массиве numbers до того числа, значение которого меньше его порядкового номера в массиве:

int[] numbers = < 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 >; var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index); Console.WriteLine(string.Join(" ", firstSmallNumbers)); // Output: // 5 4 

Лямбда-выражения не используются непосредственно в выражениях запросов, но их можно использовать в вызовах методов в выражениях запросов, как показано в следующем примере:

var numberSets = new List < new[] < 1, 2, 3, 4, 5 >, new[] < 0, 0, 0 >, new[] < 9, 8 >, new[] < 1, 0, 1, 0, 1, 0, 1, 0 >>; var setsWithManyPositives = from numberSet in numberSets where numberSet.Count(n => n > 0) > 3 select numberSet; foreach (var numberSet in setsWithManyPositives) < Console.WriteLine(string.Join(" ", numberSet)); >// Output: // 1 2 3 4 5 // 1 0 1 0 1 0 1 0 

Определение типа в лямбда-выражениях

При написании лямбда-выражений обычно не требуется указывать тип входных параметров, так как компилятор может выводить этот тип на основе тела лямбда-выражения, типов параметров и других факторов, как описано в спецификации языка C#. Для большинства стандартных операторов запросов первой входное значение имеет тип элементов в исходной последовательности. При запросе IEnumerable входная переменная считается объектом Customer , а это означает, что у вас есть доступ к его методам и свойствам:

customers.Where(c => c.City == "London"); 

Общие правила определения типа для лямбда-выражений формулируются следующим образом:

  • лямбда-выражение должно содержать то же число параметров, что и тип делегата;
  • каждый входной параметр в лямбда-выражении должен быть неявно преобразуемым в соответствующий параметр делегата;
  • возвращаемое значение лямбда-выражения (если таковое имеется) должно быть неявно преобразуемым в возвращаемый тип делегата.

Естественный тип лямбда-выражения

Лямбда-выражение само по себе не имеет типа, так как система общих типов не имеет встроенной концепции "лямбда-выражения". Однако иногда удобно говорить о "типе" лямбда-выражения. Под неофициальным термином "тип" понимается тип делегата или тип Expression, в который преобразуется лямбда-выражение.

Начиная с C# 10, лямбда-выражение может иметь естественный тип. Вам не потребуется объявлять тип делегата, например Func <. >или Action <. >для лямбда-выражения, потому что компилятор может вывести тип делегата из лямбда-выражения. В качестве примера рассмотрим следующее объявление:

var parse = (string s) => int.Parse(s); 

Компилятор может определить parse как Func . Компилятор использует доступный делегат Func или Action , если он существует. Если нет, компилятор синтезирует тип делегата. Например, тип делегата синтезируется, если лямбда-выражение имеет параметры ref . Если лямбда-выражение имеет естественный тип, его можно присвоить менее явному типу, например System.Object или System.Delegate:

object parse = (string s) => int.Parse(s); // Func Delegate parse = (string s) => int.Parse(s); // Func

Группы методов (то есть имена методов без списков параметров) с ровно одной перегрузкой имеют естественный тип:

var read = Console.Read; // Just one overload; Func inferred var write = Console.Write; // ERROR: Multiple overloads, can't choose 
LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression> Expression parseExpr = (string s) => int.Parse(s); // Expression> 

Не у всех лямбда-выражений есть естественный тип. Рассмотрим следующее объявление:

var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda 

Компилятор не может определить тип параметра для s . Если компилятор не может определить естественный тип, необходимо объявить тип:

Func parse = s => int.Parse(s); 

Явный тип возвращаемого значения

Как правило, тип возвращаемого значения лямбда-выражения является очевидным и легко выводится. Для некоторых выражений, которые не работают:

var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type 

Начиная с C# 10, можно указать тип возвращаемого значения лямбда-выражения перед входными параметрами. Если вы указываете явный тип возвращаемого значения, заключите входные параметры в скобки:

var choose = object (bool b) => b ? 1 : "two"; // Func

Атрибуты

Начиная с C# 10, вы можете добавлять атрибуты в лямбда-выражение и его параметры. В следующем примере показано, как добавить атрибуты в лямбда-выражение:

Func parse = [ProvidesNullCheck] (s) => (s is not null) ? int.Parse(s) : null; 

Кроме того, вы можете добавить атрибуты во входные параметры или возвращаемое значение, как показано в следующем примере:

var concat = ([DisallowNull] string a, [DisallowNull] string b) => a + b; var inc = [return: NotNullIfNotNull(nameof(s))] (int? s) => s.HasValue ? s++ : null; 

Как показано в предыдущих примерах, при добавлении атрибутов в лямбда-выражение или его параметры вам нужно заключить входные параметры в скобки.

Лямбда-выражения вызываются через базовый тип делегата. Это отличается от методов и локальных функций. Метод делегата Invoke не проверяет атрибуты в лямбда-выражении. При вызове лямбда-выражения атрибуты не оказывают никакого влияния. Атрибуты лямбда-выражений полезны для анализа кода и могут быть обнаружены с помощью отражения. Одно из последствий этого решения — невозможность применить System.Diagnostics.ConditionalAttribute к лямбда-выражению.

Запись внешних переменных и области видимости переменной в лямбда-выражениях

Лямбда-выражения могут ссылаться на внешние переменные. Эти внешние переменные являются переменными, которые находятся в область в методе, определяющем лямбда-выражение, или в область в типе, который содержит лямбда-выражение. Переменные, полученные таким способом, сохраняются для использования в лямбда-выражениях, даже если бы в ином случае они оказались за границами области действия и уничтожились сборщиком мусора. Внешняя переменная должна быть определенным образом присвоена, прежде чем она сможет использоваться в лямбда-выражениях. В следующем примере демонстрируются эти правила.

public static class VariableScopeWithLambdas < public class VariableCaptureGame < internal Action? updateCapturedLocalVariable; internal Func? isEqualToCapturedLocalVariable; public void Run(int input) < int j = 0; updateCapturedLocalVariable = x => < j = x; bool result = j >input; Console.WriteLine($" is greater than : "); >; isEqualToCapturedLocalVariable = x => x == j; Console.WriteLine($"Local variable before lambda invocation: "); updateCapturedLocalVariable(10); Console.WriteLine($"Local variable after lambda invocation: "); > > public static void Main() < var game = new VariableCaptureGame(); int gameInput = 5; game.Run(gameInput); int jTry = 10; bool result = game.isEqualToCapturedLocalVariable!(jTry); Console.WriteLine($"Captured local variable is equal to : "); int anotherJ = 3; game.updateCapturedLocalVariable!(anotherJ); bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ); Console.WriteLine($"Another lambda observes a new value of captured variable: "); > // Output: // Local variable before lambda invocation: 0 // 10 is greater than 5: True // Local variable after lambda invocation: 10 // Captured local variable is equal to 10: True // 3 is greater than 5: False // Another lambda observes a new value of captured variable: True > 

Следующие правила применимы к области действия переменной в лямбда-выражениях.

  • Захваченная переменная не будет уничтожена сборщиком мусора до тех пор, пока делегат, который на нее ссылается, не перейдет в статус подлежащего уничтожению при сборке мусора.
  • Переменные, представленные в лямбда-выражении, невидимы в заключающем методе.
  • Лямбда-выражение не может непосредственно захватывать параметры in, ref или out из заключающего метода.
  • Оператор return в лямбда-выражении не вызывает возврат значения заключающим методом.
  • Лямбда-выражение не может содержать операторы goto, break или continue, если целевой объект этого оператора перехода находится за пределами блока лямбда-выражения. Если целевой объект находится внутри блока, использование оператора перехода за пределами лямбда-выражения также будет ошибкой.

Модификатор можно применить static к лямбда-выражению, чтобы предотвратить непреднамеренный захват локальных переменных или состояния экземпляра лямбда-выражения:

Func square = static x => x * x; 

Статическое лямбда-выражение не может сохранять локальные переменные или состояние экземпляров из охватывающих областей, но может ссылаться на статические элементы и определения констант.

Спецификация языка C#

Дополнительные сведения об этих функциях см. в следующих заметках о предложении функций:

  • Параметры лямбда-дис карта
  • Статические анонимные функции
  • Улучшения лямбда-выражений (C# 10)

См. также

  • Используйте локальную функцию вместо лямбда-правила (правило стиля IDE0039)
  • справочник по C#
  • Операторы и выражения C#
  • Встроенный язык запросов LINQ
  • Деревья выражений
  • Локальные функции или лямбда-выражения
  • Примеры запросов LINQ
  • Пример XQuery
  • Общие сведения о примерах LINQ

Совместная работа с нами на GitHub

Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *