C # лямбдаські вирази: навіщо мені їх використовувати?


309

Я швидко прочитав Microsoft Lambda Expression .

Цей приклад допоміг мені зрозуміти краще:

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

Проте я не розумію, чому це таке нововведення. Це просто метод, який гине, коли закінчується "змінна методу", правда? Чому я повинен використовувати це замість реального методу?


3
Для тих з вас , хто прийшов на цю сторінку і не знає , що delegateв C #, я настійно рекомендую читати це , перш ніж читати решту цієї сторінки: stackoverflow.com/questions/2082615 / ...
Колоб Каньйон

Відповіді:


281

Лямбда-вирази - це простіший синтаксис для анонімних делегатів і може використовуватися скрізь, коли може бути використаний анонімний делегат. Однак навпаки не вірно; лямбда-вирази можуть бути перетворені в дерева виразів, що дозволяє отримати багато магії, як LINQ, для 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 в 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 (person => person.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;};
}

Набагато простіше.


У першому прикладі, чому б не подати відправника та отримати значення?
Андрій

@Andrew: У цьому простому прикладі не потрібно використовувати відправника, оскільки існує лише один компонент, про який йде мова, і використання поля безпосередньо зберігає склад, що покращує чіткість. У реальному сценарії я особисто також вважаю за краще використовувати відправника. Зазвичай я використовую один обробник подій для кількох подій, якщо це можливо, і тому мені доводиться ідентифікувати фактичного відправника.
Кріс Тофський

35

Lambda очистив анонімний синтаксис делегата C # 2.0 ... наприклад

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

Це було зроблено на C # 2.0 так:

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

Функціонально вони роблять саме те саме, його просто набагато більш стислий синтаксис.


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, подумайте про анонімного делегата як функції, яка не має "імені". Ви все ще визначаєте функцію, яка може мати введення та вихід, але оскільки це ім'я, ви не можете посилатися на неї безпосередньо. У наведеному вище коді ви визначаєте метод (який приймає a stringі повертає a bool) як параметр для самого Findметоду.
Скотт Дорман

22

Microsoft надала нам більш чіткий, більш зручний спосіб створення анонімних делегатів під назвою Lambda expressions. Однак увазі цієї частини висловлювань приділяється не так багато уваги . 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();
        }
    }
}

Цей приклад банальний. І я впевнений, що ви думаєте: "Це марно, оскільки я міг би безпосередньо створити делегата, а не створювати вираз і компілювати його під час виконання". І ви б мали рацію. Але це забезпечує основу для виразних дерев. У просторах імен виразів доступна кількість виразів, і ви можете створити свій власний. Я думаю, ви можете побачити, що це може бути корисно, коли ви точно не знаєте, яким повинен бути алгоритм при розробці або складанні. Десь я бачив приклад використання цього для написання наукового калькулятора. Ви також можете використовувати його для байєсів систем або для генетичного програмування(AI). Кілька разів за свою кар’єру мені довелося писати функцію, схожу на Excel, яка дозволяла користувачам вводити прості вирази (додавання, віднімання тощо) для роботи над наявними даними. У pre-.Net 3.5 мені довелося вдатися до якоїсь мови скриптів, зовнішньої до C #, або довелося використовувати функцію передачі коду для відображення для створення .Net коду на льоту. Тепер я б використовував дерева виразів.


12

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

І це насправді не нововведення. LISP виконував лямбда-функції близько 30 і більше років.


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));

5

Вираз лямбда - це стислий спосіб представити анонімний метод. Як анонімні методи, так і лямбдаські вирази дозволяють визначити реалізацію методу в рядку, однак, анонімний метод явно вимагає визначити типи параметрів та тип повернення для методу. В лямбдаському виразі використовується функція виводу типу 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. Як можна взаємозамінно використовувати делегат та інтерфейс?

4

Ви багато разів використовуєте функціонал лише в одному місці, тому створення методу просто захаращує клас.


3

Це спосіб прийняти невелику операцію і поставити її дуже близько до місця, де вона використовується (не на відміну від оголошення змінної, близької до точки її використання). Це повинно зробити ваш код більш читабельним. Анонімувавши вираз, ви також ускладнюєте комусь зламати ваш клієнтський код, якщо його функцію використовують десь в іншому місці та модифікують для "покращення".

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


0

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

Наприклад, раніше ви могли використовувати SQL і могли отримати атаку ін'єкції SQL, оскільки хакер передав рядок, де зазвичай очікувалося число. Тепер ви б використовували лямбдаський вираз LINQ, який захищений від цього.

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

У 2016 році більшість популярних мов мають підтримку вираження лямбда , і C # був одним із піонерів цієї еволюції серед основних імперативних мов.


0

Це, мабуть, найкращі пояснення того, чому слід використовувати лямбда-вирази -> https://youtu.be/j9nj5dTo54Q

Підсумовуючи це, це покращити читабельність коду, зменшити ймовірність помилок шляхом повторного використання, а не тиражування коду, та використання оптимізації, яка відбувається за кадром.


0

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

Крім того, оскільки вони описують код одноразового використання, вони не повинні бути членами класу, де це призведе до більшої складності коду. Уявіть, що нам доведеться оголошувати клас з незрозумілим фокусом кожного разу, коли ми хотіли налаштувати роботу об’єкта класу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.