Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
Используйте лямбда-выражение для создания анонимной функции. Используйте оператор лямбда-объявления =>, чтобы разделить список лямбда-параметров и его тело.
Справочные документы по языку C# описывают последнюю выпущенную версию языка C#. Она также содержит начальную документацию по функциям в общедоступных предварительных версиях для предстоящего языкового выпуска.
Документация определяет любую функцию, впервые представленную в последних трех версиях языка или в текущих общедоступных предварительных версиях.
Подсказка
Чтобы узнать, когда функция впервые появилась в C#, ознакомьтесь со статьей об истории версий языка C#.
Лямбда-выражение может быть любым из следующих двух форм:
лямбда-выражение, у которого выражение в качестве тела:
(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 для определения типа делегата. Компилятор синтезирует правильный тип делегата.
Дополнительные сведения о параметрах по умолчанию для лямбда-выражений можно найти в спецификации возможностей на параметры по умолчанию для лямбда-выражений .
Асинхронные лямбда-выражения
Используя асинхронные и ожидаемые ключевые слова, можно легко создавать лямбда-выражения и инструкции, которые включают асинхронную обработку. Например, следующий пример 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#.
Дополнительные сведения об этих функциях см. в следующих заметках о предложении функций: