У чому різниця між лямбдами та делегатами в .NET Framework?


86

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


2
Під "делегатами" ви маєте на увазі типи делегатів або анонімних делегатів? Вони також різні.
Кріс Аммерман,


1
чому люди так ускладнюють його запитання? Просто дайте відповідь, що таке делегат, а що лямбда. Дайте якомога більше пояснень і нехай він вибере все, що підходить йому.
Імір Ходжа

Відповіді:


96

Це насправді дві дуже різні речі. "Делегат" - це фактично назва змінної, яка містить посилання на метод або лямбду, а лямбда - це метод без постійного імені.

Лямбди дуже схожі на інші методи, за винятком кількох тонких відмінностей.

  1. Звичайний метод визначається в "твердженні" і прив'язується до постійного імені, тоді як лямбда визначається "на льоту" у "виразі" і не має постійного імені.
  2. Деякі лямбди можна використовувати з деревами виразів .NET, тоді як методи не можуть.

Делегат визначається так:

delegate Int32 BinaryIntOp(Int32 x, Int32 y);

Змінна типу BinaryIntOp може мати або метод, або лабораторію, призначену їй, якщо підпис однаковий: два аргументи Int32 і повернення Int32.

Лямбда може бути визначена так:

BinaryIntOp sumOfSquares = (a, b) => a*a + b*b;

Інша річ, на яку слід звернути увагу, - хоча загальні типи Func та Action часто вважаються "лямбда-типами", вони подібні до будь-яких інших делегатів. Приємне в них те, що вони, по суті, визначають ім’я для будь-якого типу делегата, який вам може знадобитися (до 4 параметрів, хоча ви, звичайно, можете додати більше власних). Отже, якщо ви використовуєте найрізноманітніші типи делегатів, але не більше одного разу, ви можете уникнути захаращення коду оголошеннями делегатів, використовуючи Func та Action.

Ось ілюстрація того, як Func і Action - це "не тільки для лямбд":

Int32 DiffOfSquares(Int32 x, Int32 y)
{
  return x*x - y*y;
}

Func<Int32, Int32, Int32> funcPtr = DiffOfSquares;

Ще одне корисне, що слід знати, - це те, що типи делегатів (а не самі методи) з однаковим підписом, але різними іменами не будуть неявно передаватися один одному. Це включає делегатів Func та Action. Однак, якщо підпис ідентичний, ви можете явно робити між ними.

Надалі милі .... У C # функції гнучкі, з використанням лямбда і делегатів. Але C # не має "першокласних функцій". Ви можете використовувати ім'я функції, присвоєне змінній делегата, щоб по суті створити об'єкт, що представляє цю функцію. Але це насправді фокус компілятора. Якщо ви розпочнете оператор, написавши ім'я функції, за якою стоїть крапка (тобто спробуйте зробити доступ членів до самої функції), ви виявите, що там немає членів, на які можна посилатися. Навіть не ті з Object. Це заважає програмісту робити корисні (і, можливо, потенційно небезпечні) речі, такі як додавання методів розширення, які можна викликати для будь-якої функції. Найкраще, що ви можете зробити, - це розширити сам клас Delegate, що, безсумнівно, також корисно, але не настільки.

Оновлення: Дивіться також відповідь Карга, яка ілюструє різницю між анонімними делегатами та методами та лямбда.

Оновлення 2: Джеймс Харт робить важливе, хоча і дуже технічне зауваження, що лямбди та делегати не є сутностями .NET (тобто CLR не має поняття делегат або лямбда), а скоріше це структурні та мовні конструкції.


Гарне пояснення. Хоча, я думаю, ви маєте на увазі "першокласні функції", а не "першокласні об'єкти". :)
ibz

1
Ти маєш рацію. У мене під час написання речення було структуровано по-різному ("Функції C # насправді не є першокласними об'єктами") і забув це змінити. Дякую!
Кріс Аммерман

Нормальний метод визначається в "твердженні" . Оператор - це дія в послідовності імперативної програми, можливо, заснована на виразі. Чи не є визначення методу іншою граматичною структурою? Визначення методу не вказано в документі docs.microsoft.com/en-us/dotnet/csharp/tour-of-csharp/…
Макс

32

Питання дещо неоднозначне, що пояснює велику різницю у відповідях, які ви отримуєте.

Ви насправді запитували, в чому різниця між лямбдами та делегатами в середовищі .NET; це може бути однією з багатьох речей. Ви запитуєте:

  • У чому різниця між лямбда-виразами та анонімними делегатами на мові C # (або VB.NET)?

  • У чому різниця між System.Linq.Expressions.LambdaExpression об’єктами та System.Delegate об’єктами в .NET 3.5?

  • Або щось середнє або навколо цих крайнощів?

Деякі люди, схоже, намагаються дати вам відповідь на запитання "в чому різниця між виразами C # Lambda та .NET System.Delegate?", Яке не має сенсу.

Структура .NET сама по собі не розуміє понять анонімних делегатів, лямбда-виразів чи закриттів - це все, що визначається мовними специфікаціями. Подумайте про те, як компілятор C # переводить визначення анонімного методу в метод на сформованому класі зі змінними-членами для утримання стану закриття; у .NET в делегаті немає нічого анонімного; це просто анонім для програміста на C #, який його пише. Це однаково справедливо і для лямбда-виразу, присвоєного типу делегата.

Що .NET DOES розуміє, це ідея делегата - типу, який описує підпис методу, екземпляри якого представляють або прив'язані виклики до конкретних методів для конкретних об'єктів, або незв'язані виклики до певного методу для певного типу, проти якого можна викликати будь-який об'єкт цього типу, де зазначений метод відповідає зазначеному підпису. Усі такі типи успадковуються від System.Delegate.

.NET 3.5 також вводить простір імен System.Linq.Expressions, який містить класи для опису виразів коду, і який також може представляти пов'язані або незв'язані виклики методів для певних типів або об'єктів. Потім екземпляри LambdaExpression можуть бути скомпільовані у фактичні делегати (завдяки чому динамічний метод, заснований на структурі виразу, кодується, і повертається вказівник на нього).

У C # ви можете створювати екземпляри типів System.Expressions.Expression, присвоюючи лямбда-вираз змінній зазначеного типу, яка створить відповідний код для побудови виразу під час виконання.

Звичайно, якщо ви були запитати , що різниця між лямбда - виразів і анонімних методів в C #, в кінці кінців, то все це досить багато irelevant, і в цьому випадку основною відмінністю є стислість, яка тяжіє до анонімних делегатів , коли ви одягаєте » t дбають про параметри і не планують повертати значення, а також до лямбда, коли ви хочете, щоб параметри з типом посилання і типи повернення.

А лямбда-вирази підтримують генерацію виразів.


3
Чудова інформація! Ти надихнув мене запалити рефлектор і подивитися на ІЛ. Я не знав, що лямбди призводять до генерованих класів, але це цілком логічно тепер, коли я про це думаю.
Кріс Аммерман

20

Одна відмінність полягає в тому, що анонімний делегат може пропускати параметри, тоді як лямбда повинна відповідати точному підпису. Дано:

public delegate string TestDelegate(int i);

public void Test(TestDelegate d)
{}

його можна викликати наступними чотирма способами (зауважте, що у другому рядку є анонімний делегат, який не має жодних параметрів):

Test(delegate(int i) { return String.Empty; });
Test(delegate { return String.Empty; });
Test(i => String.Empty);
Test(D);

private string D(int i)
{
    return String.Empty;
}

Ви не можете передати лямбда-вираз, який не має параметрів, або метод, який не має параметрів. Це заборонено:

Test(() => String.Empty); //Not allowed, lambda must match signature
Test(D2); //Not allowed, method must match signature

private string D2()
{
    return String.Empty;
}

13

Делегати еквівалентні покажчикам на функції / покажчики на методи / зворотні виклики (зробіть вибір), а лямбда - це значно спрощені анонімні функції. Принаймні так я кажу людям.


Точно так! Тут немає "різниці". Це дві різні за своєю суттю речі.
ibz

3

У мене немає тонни досвіду з цим, але як би я це описав, делегат - це обгортка навколо будь-якої функції, тоді як лямбда-вираз сам по собі є анонімною функцією.


3

Делегат - це в основному лише вказівник на функцію. Лямбда може перетворитися на делегата, але він також може перетворитися на дерево виразів LINQ. Наприклад,

Func<int, int> f = x => x + 1;
Expression<Func<int, int>> exprTree = x => x + 1;

Перший рядок створює делегат, а другий - дерево виразів.


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

2

лямбди - це просто синтаксичний цукор на делегата. Закінчувач компілятора перетворює лямбди в делегати.

Я вважаю, що це те саме:

Delegate delegate = x => "hi!";
Delegate delegate = delegate(object x) { return "hi";};

2
жоден із цих прикладів не компілюється. Навіть якщо ви зміните ім'я екземпляра Delegateз "делегат", що є ключовим словом.
Стів Купер

2

Делегат - це підпис функції; щось на зразок

delegate string MyDelegate(int param1);

Делегат не реалізує тіло.

Лямбда - це виклик функції, який відповідає підпису делегата. Для вищевказаного делегата ви можете використовувати будь-що з;

(int i) => i.ToString();
(int i) => "ignored i";
(int i) => "Step " + i.ToString() + " of 10";

Однак Delegateтип погано названий; створення об'єкта типу Delegateнасправді створює змінну, яка може містити функції - будь то лямбда, статичні методи або методи класу.


Коли ви створюєте змінну типу MyDelegate, насправді це не тип виконання. Тип виконання - Delegate. Існують трюки компілятора, що беруть участь у компіляції делегатів, лямбдас та дерев виразів, які, на мою думку, спричиняють код, що означає неправдиві речі.
Кріс Аммерман

2

Делегат - це посилання на метод із певним списком параметрів і типом повернення. Він може включати або не включати об’єкт.

Лямбда-вираз - це форма анонімної функції.


2

Делегат - це Черга покажчиків на функції, виклик делегата може викликати кілька методів. Лямбда - це, по суті, анонімне оголошення методу, яке компілятор може інтерпретувати по-різному, залежно від того, в якому контексті він використовується.

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

Різниця насправді полягає в тому, що лямбда - це стислий спосіб визначити метод всередині іншого виразу, тоді як делегат - це фактичний тип об’єкта.


2

Цілком зрозуміло, що питання мало бути таким: "яка різниця між лямбдами та анонімними делегатами?" З усіх відповідей тут лише одна людина зрозуміла це правильно - головна відмінність полягає в тому, що лямбди можна використовувати для створення дерев виразів, а також делегатів.

Ви можете прочитати більше на MSDN: http://msdn.microsoft.com/en-us/library/bb397687.aspx


1

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

Лямбда походить від ідеї лямбда-числення церкви Алонцо в 1930-х роках. Це анонімний спосіб створення функцій. Вони стають особливо корисними для складання функцій

Тож, хоча деякі можуть сказати, що лямбда - це синтаксичний цукор для делегатів, я б сказав, що делегати - це міст для полегшення людей у ​​лямбдах у c #.


1

Деякі основні тут. "Делегат" насправді є назвою змінної, яка містить посилання на метод або лямбду

Це анонімний метод -

(string testString) => { Console.WriteLine(testString); };

Оскільки анонімний метод не має жодного імені, нам потрібен делегат, в якому ми можемо призначити обидва ці методи або вираз. Для напр.

delegate void PrintTestString(string testString); // declare a delegate

PrintTestString print = (string testString) => { Console.WriteLine(testString); }; 
print();

Те саме з лямбда-виразом. Зазвичай для їх використання нам потрібен делегат

s => s.Age > someValue && s.Age < someValue    // will return true/false

Для використання цього виразу ми можемо використовувати делегат func.

Func< Student,bool> checkStudentAge = s => s.Age > someValue && s.Age < someValue ;

bool result = checkStudentAge ( Student Object);

0

Лямбди - це спрощені версії делегатів. Вони мають деякі властивості закриття, як анонімні представники, але також дозволяють використовувати неявне введення тексту. Така лямбда:

something.Sort((x, y) => return x.CompareTo(y));

набагато коротше, ніж те, що ви можете зробити з делегатом:

something.Sort(sortMethod);
...

private int sortMethod(SomeType one, SomeType two)
{
    one.CompareTo(two)
}

Ви маєте на увазі лямбди - це як спрощені анонімні методи (не делеговані). Як і методи (анонімні чи ні), їх можна присвоїти змінній делегата.
Лукас,

0

Ось приклад, який я деякий час викладав у своєму кульгавому блозі. Скажімо, ви хотіли оновити мітку з робочого потоку. У мене є 4 приклади того, як оновити цю мітку з 1 до 50 за допомогою делегатів, анонімних делегатів та 2 типів лямбда.

 private void button2_Click(object sender, EventArgs e) 
     { 
         BackgroundWorker worker = new BackgroundWorker(); 
         worker.DoWork += new DoWorkEventHandler(worker_DoWork); 
         worker.RunWorkerAsync(); 
     } 

     private delegate void UpdateProgDelegate(int count); 
     private void UpdateText(int count) 
     { 
         if (this.lblTest.InvokeRequired) 
         { 
             UpdateProgDelegate updateCallBack = new UpdateProgDelegate(UpdateText); 
             this.Invoke(updateCallBack, new object[] { count }); 
         } 
         else 
         { 
             lblTest.Text = count.ToString(); 
         } 
     } 

     void worker_DoWork(object sender, DoWorkEventArgs e) 
     {   
         /* Old Skool delegate usage.  See above for delegate and method definitions */ 
         for (int i = 0; i < 50; i++) 
         { 
             UpdateText(i); 
             Thread.Sleep(50); 
         } 

         // Anonymous Method 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke((MethodInvoker)(delegate() 
             { 
                 lblTest.Text = i.ToString(); 
             })); 
             Thread.Sleep(50); 
         } 

         /* Lambda using the new Func delegate. This lets us take in an int and 
          * return a string.  The last parameter is the return type. so 
          * So Func<int, string, double> would take in an int and a string 
          * and return a double.  count is our int parameter.*/ 
         Func<int, string> UpdateProgress = (count) => lblTest.Text = count.ToString(); 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke(UpdateProgress, i); 
             Thread.Sleep(50); 
         } 

         /* Finally we have a totally inline Lambda using the Action delegate 
          * Action is more or less the same as Func but it returns void. We could 
          * use it with parameters if we wanted to like this: 
          * Action<string> UpdateProgress = (count) => lblT…*/ 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke((Action)(() => lblTest.Text = i.ToString())); 
             Thread.Sleep(50); 
         } 
     }

0

Я припускаю, що ваше питання стосується c #, а не .NET, через неоднозначність вашого запитання, оскільки .NET не отримує самостійно - тобто без c # - розуміння делегатів та лямбда-виразів.

Делегат ( звичайний , на відміну від так званих загальних делегатів, див. Згодом) слід розглядати як свого роду c ++ typedefтипу вказівника на функцію, наприклад в c ++:

R (*thefunctionpointer) ( T ) ;

typedef - це тип, thefunctionpointerякий є типом покажчиків на функцію, яка приймає об’єкт типу Tі повертає об’єкт типу R. Ви б використали це так:

thefunctionpointer = &thefunction ;
R r = (*thefunctionpointer) ( t ) ; // where t is of type T

де thefunctionбуде функція, яка приймає a Tі повертає R.

У c # ви б вибрали

delegate R thedelegate( T t ) ; // and yes, here the identifier t is needed

і ви б використовували його так:

thedelegate thedel = thefunction ;
R r = thedel ( t ) ; // where t is of type T

де thefunctionбуде функція, яка приймає a Tі повертає R. Це для делегатів, так званих звичайних делегатів.

Тепер у вас також є загальні делегати в c #, які є загальними, тобто "шаблонованими", так би мовити, використовуючи тим самим вираз c ++. Вони визначаються так:

public delegate TResult Func<in T, out TResult>(T arg);

І ви можете використовувати їх так:

Func<double, double> thefunctor = thefunction2; // call it a functor because it is
                                                // really as a functor that you should
                                                // "see" it
double y = thefunctor(2.0);

де thefunction2- функція, яка бере аргумент і повертає a double.

А тепер уявіть, що замість того, щоб thefunction2я хотів би використовувати "функцію", яка поки що ніде не визначена, заявою, і яку я ніколи пізніше не використовуватиму. Тоді c # дозволяє нам використовувати вираз цієї функції. Під виразом я маю на увазі «математичний» (або функціональних, щоб дотримуватися програм) вираження цього, наприклад: до double xя асоціююdouble x*x . В математиці ви пишете це, використовуючи латексний символ "\ mapsto" . У C # функціональне позначення було запозичене: =>. Наприклад :

Func<double, double> thefunctor = ( (double x) => x * x ); // outer brackets are not
                                                           // mandatory

(double x) => x * x- це вираз . Це не тип, тоді як делегати (загальні чи ні) є.

Мораль? Зрештою, що таке делегат (відповідно, загальний делегат), якщо не тип покажчика функції (відповідно загорнутий + розумний + загальний тип покажчика функції), так? Щось ще ! Дивіться це і те .


-1

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


1
лямбди можна використовувати як обробники подій; button.Click + = (sender, eventArgs) => {MessageBox.Show ("Клацніть"); } і викликається асинхронно новим System.Threading.Thread (() => Console.Write ("Виконується в потоці")). Start ();
Стів Купер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.