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

Что такое лямбда в программировании

  • автор:

Что такое лямбда-функции в программировании

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

Чтобы вызвать функцию, нужно указать её имя, но бывают такие функции, у которых нет имени, но их всё равно можно вызывать. Рассказываем, как это работает.

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

Как работает обычная функция

Классическая функция выглядит так:

  1. Имя функции.
  2. Тело функции.
  3. Может быть: результат, который она возвращает, но может и не возвращать.

Запишем это на 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)

Смотрите, что тут произошло:

  1. Мы сделали список с разными числами.
  2. Потом мы создали новый на основе старого, используя команду filter() — она выбирает из списка нужные элементы по критерию.
  3. В качестве такого критерия мы указали, что элемент можно брать, только если он чётный.
  4. Значения этих элементов берутся из старого списка 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, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

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

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