Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
Для создания анонимной функции используется лямбда-выражение =>, чтобы разделить список лямбда-параметров и его тело. Лямбда-выражение может иметь любой из следующих двух форм:
лямбда-выражение, у которого выражение в качестве тела:
(input-parameters) => expressionлямбда-выражение с блоком инструкций в качестве тела
(input-parameters) => { <sequence-of-statements> }
Чтобы создать лямбда-выражение, необходимо указать входные параметры (если таковые есть) слева от лямбда-оператора и выражения или блока инструкций на другой стороне.
Любое лямбда-выражение можно преобразовать в тип делегата . Типы его параметров и возвращаемое значение определяют тип делегата, в который можно преобразовать лямбда-выражение. Если лямбда-выражение не возвращает значение, его можно преобразовать в один из типов делегатов Action; в противном случае его можно преобразовать в один из типов делегатов Func. Например, лямбда-выражение, которое имеет два параметра и не возвращает значение, может быть преобразовано в делегат Action<T1,T2>. Лямбда-выражение, которое имеет один параметр и возвращает значение, можно преобразовать в делегат Func<T,TResult>. В следующем примере лямбда-выражение x => x * x, которое задает параметр с именем x и возвращает значение x в квадрате, присваивается переменной типа делегат:
Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25
Лямбда-выражения также можно преобразовать в типы дерева выражений , как показано в следующем примере:
System.Linq.Expressions.Expression<Func<int, int>> 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<T,TResult>. При вызове метода Queryable.Select в классе System.Linq.Queryable, например в LINQ to SQL, тип параметра — это тип дерева выражений Expression<Func<TSource,TResult>>. В обоих случаях для указания значения параметра можно использовать одно и то же лямбда-выражение. Это делает два вызова Select выглядеть похожими, хотя на самом деле тип объектов, созданных из лямбда-выражений, отличается.
Лямбда-выражения
Лямбда-выражение с выражением справа от оператора => называется лямбда-выражением . Лямбда-выражение возвращает результат выражения и принимает следующую базовую форму:
(input-parameters) => expression
Текст лямбда-выражения может состоять из вызова метода. Однако при создании деревьев выражений , вычисляемых поставщиком запросов, следует ограничить вызовы методов теми методами, которые поставщик запросов преобразует в его формат. Различные поставщики запросов имеют различные возможности, например, многие поставщики на основе SQL могут переводить такие методы, как String.StartsWith в соответствующие выражения SQL, например LIKE. Если поставщик запросов не распознает вызов метода, он не может перевести или выполнить выражение.
Лямбда-выражения инструкции
Лямбда с инструкцией напоминает лямбда-выражение, за исключением того, что её инструкции заключены в фигурные скобки.
(input-parameters) => { <sequence-of-statements> }
Текст лямбда-инструкции может состоять из любого количества инструкций; однако на практике обычно не более двух или трех.
Action<string> greet = name =>
{
string greeting = $"Hello {name}!";
Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!
Нельзя использовать лямбда-выражения для создания деревьев выражений.
Входные параметры лямбда-выражения
Входные параметры лямбда-выражения заключены в скобки. Укажите нулевые входные параметры с пустыми скобками:
Action line = () => Console.WriteLine();
Если лямбда-выражение имеет только один входной параметр, скобки являются необязательными:
Func<double, double> cube = x => x * x * x;
Два или более входных параметров разделены запятыми:
Func<int, int, bool> testForEquality = (x, y) => x == y;
Компилятор обычно вводит типы параметров в лямбда-выражения, называемые неявным типизированным списком параметров. Вы можете явно указать типы, называемые явным типизированным списком параметров. Список параметров явно типизированного типа показан в следующем примере. :
Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;
Типы входных параметров должны быть явными или неявными; в противном случае возникает ошибка компилятора CS0748. Перед C# 14 необходимо включить явный тип параметра, если он имеет модификаторы, например ref или out. В C# 14 это ограничение удаляется. Однако при использовании params модификатора необходимо объявить тип.
Можно использовать отменять, чтобы указать два или более входных параметров лямбда-выражения, которые не используются в выражении:
Func<int, int, int> 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 IEnumerable<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);
delegate int SumCollectionDelegate(params IEnumerable<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 {numbers} doubled: {doubledNumbers}");
// 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 {numbers} doubled: {doubledNumbers}");
Дополнительные сведения о кортежах C# см. в типах кортежей.
Лямбда со стандартными операторами запросов
LINQ to Objects, среди других реализаций, имеет входной параметр, тип которого является одним из Func<TResult> семейства универсальных делегатов. Эти делегаты используют параметры типа для определения числа и типа входных параметров, а также возвращаемого типа делегата.
Func делегаты полезны для инкапсулирования определяемых пользователем выражений, применяемых к каждому элементу в наборе исходных данных. Например, рассмотрим тип делегата Func<T,TResult>:
public delegate TResult Func<in T, out TResult>(T arg)
Делегат можно создать как экземпляр Func<int, bool>, где int является входным параметром, а bool — возвращаемым значением. Возвращаемое значение всегда указывается в последнем параметре типа. Например, Func<int, string, bool> определяет делегат с двумя входными параметрами, int и stringи возвращаемым типом bool. Следующий Func делегат при вызове возвращает логическое значение, указывающее, равен ли входной параметр пяти:
Func<int, bool> equalsFive = x => x == 5;
bool result = equalsFive(4);
Console.WriteLine(result); // False
Можно также указать лямбда-выражение, если тип аргумента является Expression<TDelegate>, например в стандартных операторах запросов, определенных в типе Queryable. Когда указываете аргумент Expression<TDelegate>, лямбда компилируется в дерево выражений.
В следующем примере используется стандартный оператор запроса 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 {oddNumbers} odd numbers in {string.Join(" ", numbers)}");
Компилятор может определить тип входного параметра или явно указать его. Это конкретное лямбда-выражение подсчитывает эти целые числа (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<int[]>
{
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>, то входная переменная выводится в виде объекта Customer, что означает, что у вас есть доступ к его методам и свойствам:
customers.Where(c => c.City == "London");
Общие правила вывода типов для лямбда-кодов приведены следующим образом:
- Лямбда-код должен содержать то же количество параметров, что и тип делегата.
- Каждый входной параметр в лямбда-выражении должен быть неявно преобразуем в соответствующий параметр делегата.
- Возвращаемое значение лямбда-выражения (если оно есть) должно быть неявно преобразуемо в тип возвращаемого значения делегата.
Естественный тип лямбда-выражения
Лямбда-выражение само по себе не имеет типа, так как система общих типов не имеет встроенной концепции "лямбда-выражения". Однако иногда удобно говорить о "типе" лямбда-выражения. Этот неформальный "type" относится к типу делегата или типу Expression, в который преобразуется лямбда-выражение.
Лямбда-выражение может иметь естественный тип. Вместо принудительного объявления типа делегата, например Func<...> или Action<...> для лямбда-выражения, компилятор может выводить тип делегата из лямбда-выражения. Например, рассмотрим следующее объявление:
var parse = (string s) => int.Parse(s);
Компилятор может сделать вывод, что parse является Func<string, int>. Компилятор выбирает доступный делегат Func или Action, если он подходит. В противном случае система синтезирует тип делегата. Например, если лямбда-выражение имеет параметры ref, тип делегата синтезируется. Если лямбда-выражение имеет естественный тип, его можно назначить менее явному типу, например System.Object или System.Delegate:
object parse = (string s) => int.Parse(s); // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>
Группы методов (то есть имена методов без списков параметров) с точно одной перегрузкой имеют естественный тип:
var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose
При назначении лямбда-выражения на System.Linq.Expressions.LambdaExpressionили System.Linq.Expressions.Expression, и если лямбда-выражение имеет естественный тип делегата, выражение имеет естественный тип System.Linq.Expressions.Expression<TDelegate>, с естественным типом делегата, используемым в качестве аргумента для параметра типа.
LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Не все лямбда-выражения имеют естественный тип. Рассмотрим следующее объявление:
var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda
Компилятор не может определить тип параметра для s. Если компилятор не может вывести естественный тип, необходимо объявить тип:
Func<string, int> parse = s => int.Parse(s);
Явный тип возвращаемого значения
Как правило, возвращаемый тип лямбда-выражения очевиден и выведен. Для некоторых выражений, которые не работают:
var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type
Вы можете указать тип возвращаемого значения лямбда-выражения перед входными параметрами. При указании явного типа возвращаемого значения необходимо скобить входные параметры:
var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>
Атрибуты
Атрибуты можно добавить в лямбда-выражение и его параметры. В следующем примере показано, как добавить атрибуты в лямбда-выражение:
Func<string?, int?> 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<int>? updateCapturedLocalVariable;
internal Func<int, bool>? isEqualToCapturedLocalVariable;
public void Run(int input)
{
int j = 0;
updateCapturedLocalVariable = x =>
{
j = x;
bool result = j > input;
Console.WriteLine($"{j} is greater than {input}: {result}");
};
isEqualToCapturedLocalVariable = x => x == j;
Console.WriteLine($"Local variable before lambda invocation: {j}");
updateCapturedLocalVariable(10);
Console.WriteLine($"Local variable after lambda invocation: {j}");
}
}
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 {jTry}: {result}");
int anotherJ = 3;
game.updateCapturedLocalVariable!(anotherJ);
bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ);
Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}");
}
// 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
}
Следующие правила применяются к области переменной в лямбда-выражениях:
- Захваченная переменная не подлежит сборке мусора, пока делегат, ссылающийся на неё, не станет доступным для сборки мусора.
- Переменные, введенные в лямбда-выражение, не отображаются в заключаемом методе.
- Лямбда-выражение не может напрямую захватывать параметр в, refпараметр или out параметр из охватывающего метода.
- Инструкция возврата в лямбда-выражении не приводит к возврату включающего метода.
- Лямбда-выражение не может содержать goto, breakили continue оператор, если цель этого оператора перехода находится вне блока лямбда-выражения. Это также ошибка иметь инструкцию перехода за пределами блока лямбда-выражений, если целевой объект находится внутри блока.
Модификатор static можно применить к лямбда-выражению, чтобы предотвратить непреднамеренный захват локальных переменных или состояния экземпляра лямбда-выражения:
Func<double, double> square = static x => x * x;
Статическая лямбда-выражение не может захватывать локальные переменные или состояние экземпляра из внешних областей видимости, но может ссылаться на статические члены и определения констант.
Спецификация языка C#
Дополнительные сведения см. в разделе Анонимные выражения функций спецификации языка C#.
Дополнительные сведения об этих функциях см. в следующих заметках о предложении функций: