C # Лямбда-выражения: зачем их использовать?

310

Я быстро прочитал документацию по Microsoft Lambda Expression .

Этот пример помог мне лучше понять:

delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25

Тем не менее, я не понимаю, почему это такая инновация. Это просто метод, который умирает, когда «переменная метода» заканчивается, верно? Почему я должен использовать это вместо реального метода?

Патрик Дежарден
источник
3
Для тех из вас, кто заходит на эту страницу и не знает, что такое delegateC #, я настоятельно рекомендую прочитать ее перед прочтением остальной части этой страницы: stackoverflow.com/questions/2082615/…
Kolob Canyon

Ответы:

282

Лямбда-выражения представляют собой более простой синтаксис для анонимных делегатов и могут использоваться везде, где может использоваться анонимный делегат. Однако обратное неверно; Лямбда-выражения могут быть преобразованы в деревья выражений, что позволяет использовать магию, такую ​​как LINQ to SQL.

Ниже приведен пример выражения LINQ to Objects, в котором используются анонимные делегаты, а затем лямбда-выражения, чтобы показать, насколько они проще для глаз:

// anonymous delegate
var evens = Enumerable
                .Range(1, 100)
                .Where(delegate(int x) { return (x % 2) == 0; })
                .ToList();

// lambda expression
var evens = Enumerable
                .Range(1, 100)
                .Where(x => (x % 2) == 0)
                .ToList();

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

Деревья выражений - это очень мощная новая функция C # 3.0, которая позволяет API смотреть на структуру выражения, а не просто получать ссылку на метод, который может быть выполнен. API просто должен превратить параметр делегата в Expression<T>параметр, а компилятор сгенерирует дерево выражений из лямбды, а не анонимного делегата:

void Example(Predicate<int> aDelegate);

называется как:

Example(x => x > 5);

будет выглядеть так:

void Example(Expression<Predicate<int>> expressionTree);

Последний получит представление абстрактного синтаксического дерева, которое описывает выражение x > 5. LINQ to SQL полагается на это поведение, чтобы иметь возможность превращать выражения C # в выражения SQL, требуемые для фильтрации / упорядочивания / и т. Д. На стороне сервера.

Нил Уильямс
источник
1
Без замыканий вы можете использовать статические методы в качестве обратных вызовов, но вам все равно придется определять эти методы в некотором классе, почти наверняка расширяя область применения такого метода за пределы предполагаемого использования.
ДК.
10
FWIW, вы можете иметь замыкания с анонимным делегатом, поэтому вам не нужны лямбды для этого. Лямбды в высшей степени удобочитаемее, чем анонимные делегаты, без которых использование Linq заставило бы вас кровоточить.
Бенджол
138

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

Рассмотрим этот пример:

 string person = people.Find(person => person.Contains("Joe"));

против

 public string FindPerson(string nameContains, List<string> persons)
 {
     foreach (string person in persons)
         if (person.Contains(nameContains))
             return person;
     return null;
 }

Это функционально эквивалентно.

Джозеф Дейгл
источник
8
Как был бы определен метод Find () для обработки этого лямбда-выражения?
Патрик Дежарден
3
Предикат <T> - это то, что ожидает метод Find.
Даррен Копп
1
Поскольку мое лямбда-выражение совпадает с контрактом для Predicate <T>, метод Find () принимает его.
Джозеф Дейгл
Вы имели в виду "string person = people.Find (people => people.Contains (" Joe "));"
Герн Бланстон
5
@FKCoder, нет, он не знает, хотя, возможно, было бы яснее, если бы он сказал "string person = people.Find (p => p.Contains (" Joe "));"
Бенджол
84

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

private ComboBox combo;
private Label label;

public CreateControls()
{
    combo = new ComboBox();
    label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += new EventHandler(combo_SelectedIndexChanged);
}

void combo_SelectedIndexChanged(object sender, EventArgs e)
{
    label.Text = combo.SelectedValue;
}

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

public CreateControls()
{
    ComboBox combo = new ComboBox();
    Label label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += (s, e) => {label.Text = combo.SelectedValue;};
}

Намного легче.

Агнешка
источник
В первом примере, почему бы не разыграть sender и получить значение?
Андрей
@Andrew: в этом простом примере нет необходимости использовать отправителя, потому что речь идет только об одном компоненте, и использование поля напрямую сохраняет приведение, что улучшает ясность. В реальном сценарии я лично предпочел бы также использовать отправителя. Обычно я использую один обработчик событий для нескольких событий, если это возможно, и поэтому я должен идентифицировать фактического отправителя.
Крис Топски
35

Лямбда очистил синтаксис анонимного делегата C # 2.0 ... например

Strings.Find(s => s == "hello");

Было сделано в C # 2.0 следующим образом:

Strings.Find(delegate(String s) { return s == "hello"; });

Функционально, они делают то же самое, это просто более лаконичный синтаксис.

FlySwat
источник
3
Это не совсем одно и то же - как указывает @Neil Williams, вы можете извлечь AST лямбда-выражения, используя деревья выражений, тогда как анонимные методы нельзя использовать одинаково.
LJS
это одно из многих других преимуществ лямбды. Это помогает понять код лучше, чем анонимные методы. конечно, это не намерение создавать лямбды, но это сценарий, где его можно использовать чаще.
Гуруджи
29

Это всего лишь один из способов использования лямбда-выражения. Вы можете использовать лямбда-выражение везде, где можете использовать делегат. Это позволяет вам делать такие вещи:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

strings.Find(s => s == "hello");

Этот код будет искать в списке запись, которая соответствует слову «привет». Другой способ сделать это - передать делегат методу Find, например так:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

private static bool FindHello(String s)
{
    return s == "hello";
}

strings.Find(FindHello);

РЕДАКТИРОВАТЬ :

В C # 2.0 это можно сделать с помощью синтаксиса анонимного делегата:

  strings.Find(delegate(String s) { return s == "hello"; });

Лямбда значительно убрала этот синтаксис.

Скотт Дорман
источник
2
@Jonathan Holland: Спасибо за редактирование и добавление синтаксиса анонимного делегата. Это завершает пример красиво.
Скотт Дорман
что такое анонимный делегат? // извините, я новичок в c #
HackerMan
1
@HackerMan, думайте об анонимном делегате как о функции, у которой нет «имени». Вы по-прежнему определяете функцию, которая может иметь ввод и вывод, но поскольку это имя, вы не можете обращаться к нему напрямую. В приведенном выше коде вы определяете метод (который принимает stringи возвращает a bool) в качестве параметра самого Findметода.
Скотт Дорман
22

Microsoft предоставила нам более чистый и удобный способ создания анонимных делегатов, называемых лямбда-выражениями. Однако не так много внимания уделяется части выражений этого утверждения. Microsoft выпустила целое пространство имен System.Linq.Expressions , которое содержит классы для создания деревьев выражений на основе лямбда-выражений. Деревья выражений состоят из объектов, которые представляют логику. Например, x = y + z - это выражение, которое может быть частью дерева выражений в .Net. Рассмотрим следующий (простой) пример:

using System;
using System.Linq;
using System.Linq.Expressions;


namespace ExpressionTreeThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, int>> expr = (x) => x + 1; //this is not a delegate, but an object
            var del = expr.Compile(); //compiles the object to a CLR delegate, at runtime
            Console.WriteLine(del(5)); //we are just invoking a delegate at this point
            Console.ReadKey();
        }
    }
}

Этот пример тривиален. И я уверен, что вы думаете: «Это бесполезно, поскольку я мог бы непосредственно создать делегат вместо создания выражения и его компиляции во время выполнения». И ты был бы прав. Но это обеспечивает основу для деревьев выражений. Существует несколько выражений, доступных в пространствах имен Expressions, и вы можете создавать свои собственные. Я думаю, вы можете видеть, что это может быть полезно, когда вы не знаете точно, каким должен быть алгоритм во время проектирования или компиляции. Я где-то видел пример использования этого для написания научного калькулятора. Вы также можете использовать его для байесовского систем или для генетического программирования(AI). Несколько раз в моей карьере мне приходилось писать функциональность, подобную Excel, которая позволяла пользователям вводить простые выражения (сложение, подстановки и т. Д.) Для работы с доступными данными. В pre-.Net 3.5 мне приходилось прибегать к некоторому языку сценариев, внешнему по отношению к C #, или использовать отражающую функцию кода в отражении для создания .Net-кода на лету. Теперь я бы использовал деревья выражений.

Джейсон Джексон
источник
12

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

И это не совсем инновация. LISP имел лямбда-функции около 30 лет и более.

workmad3
источник
6

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

Например: универсальная функция для расчета времени, затраченного на вызов метода. (т.е. Actionздесь)

public static long Measure(Action action)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    action();
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

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

var timeTaken = Measure(() => yourMethod(param));

Выражение позволяет вам получить возвращаемое значение из вашего метода, а также из параметра

var timeTaken = Measure(() => returnValue = yourMethod(param, out outParam));
Gunasekaran
источник
5

Лямбда-выражение - это краткий способ представления анонимного метода. Как анонимные методы, так и лямбда-выражения позволяют вам определять реализацию метода inline, однако анонимный метод явно требует, чтобы вы определили типы параметров и тип возвращаемого значения для метода. Лямбда-выражение использует функцию вывода типа C # 3.0, которая позволяет компилятору выводить тип переменной на основе контекста. Это очень удобно, потому что это экономит нам много печатать!

Виджеш В.П.
источник
5

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

delegate int MyDelagate (int i);
MyDelagate delSquareFunction = x => x * x;

Рассмотрим лямбда-выражение x => x * x;

Значением входного параметра является x (слева от =>)

Логика функции: x * x (справа от =>)

Код лямбда-выражения может быть блоком выражения вместо выражения.

x => {return x * x;};

пример

Примечание: Funcэто предопределенный общий делегат.

    Console.WriteLine(MyMethod(x => "Hi " + x));

    public static string MyMethod(Func<string, string> strategy)
    {
        return strategy("Lijo").ToString();
    }

Ссылки

  1. Как делегат и интерфейс могут использоваться взаимозаменяемо?
LCJ
источник
4

В большинстве случаев вы используете функциональность только в одном месте, поэтому создание метода просто загромождает класс.

Даррен Копп
источник
3

Это способ взять небольшую операцию и поместить ее очень близко к месту, где она используется (мало чем отличается от объявления переменной, близкой к точке ее использования). Это должно сделать ваш код более читабельным. Анонимизируя выражение, вы также значительно усложняете кому-то нарушать ваш клиентский код, если эта функция используется где-то еще и модифицируется для ее «улучшения».

Точно так же, почему вы должны использовать foreach? Вы можете делать все в foreach с простым циклом for или просто используя IEnumerable напрямую. Ответ: вам это не нужно, но это делает ваш код более читабельным.

плинтус
источник
0

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

Например, прежде чем вы могли использовать SQL и могли получить атаку SQL-инъекцией, потому что хакер передал строку, где обычно ожидалось число. Теперь вы должны использовать лямбда-выражение LINQ, которое защищено от этого.

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

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

battlmonstr
источник
0

Это, пожалуй, лучшее объяснение того, почему использовать лямбда-выражения -> https://youtu.be/j9nj5dTo54Q

Таким образом, это должно улучшить читаемость кода, уменьшить вероятность ошибок за счет повторного использования, а не репликации кода, и оптимизировать использование за кулисами.

coffeeeee
источник
0

Самым большим преимуществом лямбда-выражений и анонимных функций является тот факт, что они позволяют клиенту (программисту) библиотеки / фреймворка внедрять функциональность с помощью кода в данной библиотеке / фреймворке (так как это LINQ, ASP.NET Core и многие другие) таким образом, что обычные методы не могут. Однако их сила не очевидна для одного прикладного программиста, а для того, кто создает библиотеки, которые впоследствии будут использоваться другими, которые захотят настроить поведение кода библиотеки или тот, который использует библиотеки. Таким образом, контекст эффективного использования лямбда-выражения - это использование / создание библиотеки / фреймворка.

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

Григорис Димитроулакос
источник