Что такое лямбда-функции в программировании
Функции в языках программирования — это такие небольшие автономные программы, которые работают внутри основного кода. При запуске они могут взять какие-то стартовые параметры, обработать их, получить свой результат и отдать этот результат в основной код.
Чтобы вызвать функцию, нужно указать её имя, но бывают такие функции, у которых нет имени, но их всё равно можно вызывать. Рассказываем, как это работает.
Мы будем рассказывать про лямбда-функции на примере языка Python. В других языках лямбда-функции выглядят немного иначе, но принцип работы у них точно такой же.
Как работает обычная функция
Классическая функция выглядит так:
- Имя функции.
- Тело функции.
- Может быть: результат, который она возвращает, но может и не возвращать.
Запишем это на Python, чтобы было наглядно:
def add(x, y): result = x + y return result print(add(2,3))
У этой функции есть имя add . Есть то, что она делает внутри: складывает два числа. И есть результат, который она возвращает, — он хранится в переменной result . Чтобы вызвать эту функцию, мы указываем её имя и аргументы, которые ей нужно обработать: print(add(2,3)) .
Сейчас функция кажется избыточной, мол, сложение чисел можно сделать и так. Но саму эту функцию внутри можно сделать намного сложнее. Например, что могла бы делать эта функция:
- Проверять, какие данные ей подали на вход, и в зависимости от их типа складывать их по-разному. Например, если ей дадут две строки, она может выдать ошибку; а может соединить строки.
- Складывать не числа, а массивы из чисел. Правила сложения массивов нужно будет прописать.
- Вести учёт всех сложений, которые она делала.
- Преобразовывать входящие или исходящие данные.
- Возвращать данные не в виде числа, а в виде целого объекта с кучей свойств, если это нужно программе.
И это только банальное сложение. Всю эту логику удобно упаковать внутрь функции и вызывать с помощью простого слова add() .
Что такое лямбда-функция
Лямбда-функция выглядит иначе: у неё нет имени, но есть ключевое слово lambda — оно показывает, что дальше пойдёт безымянная функция. В общем виде лямбда-функция выглядит так:
lambda переменные: значение функции
Чтобы эта функция могла куда-то вернуть своё значение, лямбда-функции присваивают переменным — и после этого их можно использовать как обычные функции. Перепишем наш пример со сложением в виде лямбда-функции:
result = lambda x, y: x + y print(result(2,3))
Если мы запустим оба фрагмента кода, то в обоих случаях увидим на экране число 5.
Может показаться, что во втором случае у нас всё равно получилась функция result, просто записанная иначе, и нет никакой разницы с первым способом. Но на самом деле различия есть: обычные функции не получится использовать на месте переменных, а лямбда-функции — можно. Про это расскажем чуть ниже.
Ограничение лямбда-функций
Значение лямбда-функции в Python должно быть записано в одну строку. Это значит, что внутри лямбда-функций не может быть циклов, других функций и всего остального, что требует для записи нескольких строк.
Ещё такая функция обязательно возвращает какое-то значение — нельзя создать лямбда-функцию, которая ничего не вернёт. Смысл таких функций как раз в том, чтобы быстро что-то считать и сразу возвращать результат этих вычислений. Если нужна функция, которая ничего не возвращает, — используйте обычные, там так можно.
Также в лямбдах нет присваивания — это простое выражение, которому не нужно ничего промежуточно хранить, а нужно лишь посчитать.
Откуда такое название
Название лямбда-функций пришло в программирование из математического λ-исчисления, где λ — это как раз греческая буква «лямбда». Смысл там в том, что всё построено на переменных и их взаимодействии друг с другом. Условно, чтобы посчитать одно выражение, нужно найти значение всех его значений, тоже выраженное каким-то формулами, а потом подставить нужный параметр вместо переменной.
В лямбда-функциях всё то же самое:
- есть выражение, заданное с помощью переменных;
- есть общее значение, которое зависит от этого выражения;
- когда мы подставляем в лямбда-функцию какое-то значение, функция обрабатывает его и получает определённый результат;
- этот результат будет влиять на итог общего значения.
Зачем нужны лямбда-функции
Самая популярная область применения лямбда-функций — в качестве аргументов в других функциях. Мы с этим ещё столкнёмся в новых проектах, а пока пара примеров.
Допустим, у нас есть список с числами, из которого нам нужно отобрать только чётные. Для этого можно писать классическую функцию, которая переберёт каждый элемент списка и, если он чётный, добавит его в итоговый результат. А можно использовать лямбду и встроить фильтр сразу в момент создания нового списка:
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9] new_lst = list(filter(lambda x: (x%2 == 0) , lst)) print(new_lst)
Смотрите, что тут произошло:
- Мы сделали список с разными числами.
- Потом мы создали новый на основе старого, используя команду filter() — она выбирает из списка нужные элементы по критерию.
- В качестве такого критерия мы указали, что элемент можно брать, только если он чётный.
- Значения этих элементов берутся из старого списка lst.
Это значит, что в момент создания нового списка функция прошла по старому, нашла там все чётные элементы и добавила их в новый список. Так получилось быстрее и проще, чем городить отдельную функцию для выбора.
Вот пример посложнее, но нагляднее: вывести словарь в порядке убывания суммы каждого значения. Алгоритму нужно сначала найти сумму значений каждого элемента словаря, отсортировать их по убыванию, а потом запомнить новый порядок вывода. Для этого используют функцию sorted() — она сортирует словарь по определённому критерию, но вот сам критерий проще всего определить с лямбдой:
bigrams = sorter = sorted(bigrams, key=lambda key: sum(bigrams[key]), reverse=True) for key in sorter: print(key, bigrams[key])
Здесь key — это как раз критерий фильтра, в котором мы используем лямбда-функцию. Эта функция считает сумму значений каждого элемента словаря и превращает это в критерий сортировки. Функции остаётся только быстро пробежать по всему словарю, используя готовые суммы, и отсортировать его в обратном направлении:
А вот если бы мы сделали это в виде обычной функции, получилось бы сложнее и не так очевидно. Сравните этот код с предыдущим — он делает то же самое, но выглядит более громоздко:
from functools import partial def sort_func(key, dict): return sum(dict[key]) bigrams = partial_sort = partial(sort_func, dict=bigrams) sorter = sorted(bigrams.keys(), key=partial_sort, reverse=True) for key in sorter: print(key, bigrams[key])
Ещё лямбда-функции иногда используют для создания замыканий — функций внутри функций, которые тоже зависят от входных параметров:
def addition(x): return lambda y: x + y add_to_ten = addition(10) print(add_to_ten(8)) print(add_to_ten(6))
Получается, что технически можно обойтись и без лямбда-функций, но с ними иногда получается удобнее. Это узкоспециализированный инструмент, который не нужен в каждом коде, но с ним некоторые вещи становятся гораздо проще.
Лямбда-выражения в Java — что это такое, зачем нужны и как выглядят
Лямбда-выражения или анонимные функции встречаются во многих языках программирования. Рассказываем про лямбда-выражения в Java с примерами.
Лямбда-выражения встречаются в разных языках программирования: JavaScript, PHP, C# и других. В этой статье поговорим о лямбда-выражениях в Java.
Для чего хорош Java?
Лямбда-выражения или анонимные функции — это блоки кода с параметрами, которые можно вызвать из другого места программы. Они называются анонимными, потому что в отличие от функций, у них нет имён. Слово «лямбда» пришло из лямбда-исчисления, которое придумал профессор Алонзо Чёрч. Он использовал греческую букву лямбда (λ), чтобы отметить параметры.
Лямбда-выражения в Java
- Присутствуют начиная с 8 версии.
- Являются анонимными классами, реализующими метод функционального интерфейса.
- Имеют доступ только к final (или effectively final) переменным из охватывающей области видимости (для потокобезопасности).
- Не могут возвращать значение в каких-то ветках, а в других не возвращать.
- Позволяют уменьшить количество кода и повысить его читаемость.
Примеры синтаксиса лямбда-выражений в Java
Лямбда-выражения в Java состоят из параметров и стрелки —> отделяющей тело функции. Скобки нужны, если параметров 0 или больше одного. Для однострочных лямбд ключевое слово return не обязательно.
(список параметров) -> тело лямбды
Без параметров
@FunctionalInterface interface MyFunctionalInterface < //метод без параметров public String sayHello(); >public class Example < public static void main(String args[]) < // лямбда выражение MyFunctionalInterface msg = () ->< return "Привет мир"; >; System.out.println(msg.sayHello()); > >
Этот код выведет в консоль текст: «Привет мир!».
C параметром
import java.awt.*; public class ButtonListenerNewWay < public static void main(String[] args) < Frame frame=new Frame("ActionListener java8"); Button b=new Button("Click Here"); b.setBounds(50,100,80,50); b.addActionListener(e ->System.out.println("Привет мир!")); frame.add(b); frame.setSize(200,200); frame.setLayout(null); frame.setVisible(true); > >
В этом примере лямбда-выражение используется для обработки нажатия кнопки.
С несколькими параметрами
interface StringConcat < public String sconcat(String a, String b); >public class Example < public static void main(String args[]) < StringConcat s = (str1, str2) ->str1 + str2; System.out.println("Result: "+s.sconcat("Hello ", "World")); > >
Здесь лямбда-выражение склеивает строки.
Объяснение лямбда-выражений
У меня возникли вопросы о лямбда-выражениях и RxJava. Эти вопросы в основном касаются не полного понимания лямбда-выражений или RxJava. Я попытаюсь объяснить лямбда-выражения как можно проще. RxJava я опишу отдельно.
Лямбда-выражения и RxJava
Что такое лямбда-выражения? Лямбда-выражения – это «всего лишь» новый способ сделать то же самое, что мы всегда могли сделать, но в более чистом и менее многословном новом способе использования анонимных внутренних классов.
Анонимный внутренний класс в Java – это класс без имени, он должен использоваться, если вам необходимо переопределить методы класса или интерфейса. Анонимный внутренний класс может быть создан из класса или интерфейса.
abstract class Animal < abstract void speak(); >Animal a = new Animal() < void speak() < System.out.println("Woff"); >>;
В Android мы обычно используем анонимный внутренний класс в качестве слушателя, например, для кнопок такого рода:
Button btn = findViewById(R.id.button); btn.setOnClickListener( new View.OnClickListener() < @Override public void onClick(final View view) < // Do some fancy stuff with the view parameter. >> );
Вернемся к лямбда-выражениям. Это следующая часть, которая является частью предыдущего кода, который считается анонимным внутренним классом.
new View.OnClickListener() < @Override public void onClick(final View view) < // Do some fancy stuff with the view parameter. >>
Лямбда-выражения могут использоваться только в том случае, если вам нужно переопределить не более одного метода. К счастью для нас, View.OnClickListener содержит только один. Посмотрите на код ниже. Как думаете, какую его часть нам придётся убрать?
new View.OnClickListener() < @Override public void onClick(final View view) < // Do some fancy stuff with the view parameter. >>
После удаления практически всего кода, нам нужно добавить ->, как в приведенном ниже коде. Входящий параметр view может использоваться внутри функции так же, как и раньше.
(view) -> < // Do some fancy stuff with the view parameter. >
В некоторых случаях вам может потребоваться добавить тип к параметру, если компилятору не удается его угадать, вы можете сделать это, добавив его перед параметром:
(View view) -> < // Do some fancy stuff with the view parameter. >
Вы также можете использовать многострочный код:
(view) ->
Если у вас есть интерфейс с методом, принимающим два параметра…
interface MyInterface
…лямбда-выражение будет выглядеть следующим образом:
Если метод имеет возвращаемый тип…
interface MySecondInterface
…лямбда-выражение будет выглядеть следующим образом:
Но это еще не все, есть некоторые специальные случаи, которые делают код еще меньше. Если тело вашего метода содержит только одну строку кода, вы можете удалить фигурные скобки <>. Если вы удаляете фигурные скобки, вам также необходимо удалить точку с запятой, оставив следующее:
(a, b) -> return a + b
Есть еще одна вещь, которую мы можем сделать. Если у нас только одна строка кода, то компилятор может понять, нужна ли возвращаемая часть или нет, поэтому мы можем оставить ее следующим образом:
(a, b) -> a + b
Если бы у нас был многострочный код, это свелось бы к следующему:
Так что же мы сделали? Мы взяли это:
new MySecondInterface() < @Override public int onSecondMethod(final int a, final int b) < return a + b; >>;
и превратили вот в это:
(a, b) -> a + b
Осталось только одна вещь, ссылки на методы. Допустим, у нас есть интерфейс, как и раньше, и метод, который этот интерфейс принимает как параметр:
public interface Callback < public void onEvent(int event); >public void myMethod(Callback callback)
Без лямбда-выражения это выглядело бы так:
myMethod(new Callback() < @Override public void onEvent(final int state) < System.out.println(state); >>);
Добавив лямбда-выражение, как мы это делали раньше, получим следующее:
myMethod(state -> System.out.println(state));
Но это еще не все. Если используемый код – однострочный и вызываемая функция принимает один параметр, мы можем передавать ссылку на метод в таком виде:
myMethod(System.out::println);
Параметр будет передаваться автоматически, без необходимости использования другого кода! Удивительно, правда? Надеюсь, вы узнали что-то новое!
- android development
- android
- java
- rxJava
- реактивное программирование
- перевод с английского
- программирование
- разработка
- devcolibri
- андроид
- джава
- никто не читает теги
- Программирование
- Java
- Разработка мобильных приложений
- Разработка под Android
Лямбда-выражения и анонимные функции
Лямбда-выражение используется для создания анонимной функции. Используйте оператор объявления лямбда-выражения => для отделения списка параметров лямбда-выражения от исполняемого кода. Лямбда-выражение может иметь одну из двух следующих форм:
-
Лямбда выражения, имеющая выражение в качестве текста:
(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, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.