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


86

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


2
Под «делегатами» вы подразумеваете типы делегатов или анонимные делегаты? Они тоже разные.
Крис Аммерман,


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

Ответы:


96

На самом деле это две очень разные вещи. «Делегат» - это на самом деле имя переменной, которая содержит ссылку на метод или лямбда, а лямбда - это метод без постоянного имени.

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

  1. Обычный метод определяется в «заявлении» и привязан к постоянному имени, тогда как лямбда определяется «на лету» в «выражении» и не имеет постоянного имени.
  2. Некоторые лямбды можно использовать с деревьями выражений .NET, а методы - нет.

Делегат определяется так:

delegate Int32 BinaryIntOp(Int32 x, Int32 y);

Переменной типа BinaryIntOp может быть назначен метод или labmda, если подпись одна и та же: два аргумента 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 # и .NET System.Delegate?», Который не имеет большого смысла.

Платформа .NET сама по себе не понимает концепций анонимных делегатов, лямбда-выражений или замыканий - все это определяется спецификациями языка. Подумайте о том, как компилятор C # преобразует определение анонимного метода в метод сгенерированного класса с переменными-членами для хранения состояния закрытия; в .NET в делегате нет ничего анонимного; это просто анонимно для программиста на C #, пишущего его. Это в равной степени верно и для лямбда-выражения, присвоенного типу делегата.

.NET ДЕЙСТВИТЕЛЬНО понимает идею делегата - типа, который описывает сигнатуру метода, экземпляры которой представляют либо связанные вызовы определенных методов для определенных объектов, либо несвязанные вызовы определенного метода для определенного типа, который может быть вызван для любой объект этого типа, в котором указанный метод придерживается указанной подписи. Все такие типы наследуются от 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с '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, на самом деле это не тип среды выполнения. Тип среды выполнения - Делегат. Есть уловки компилятора, связанные с тем, как компилируются делегаты, лямбда-выражения и деревья выражений, которые, как я думаю, вызывают то, что код подразумевает вещи, которые не соответствуют действительности.
Крис Аммерман,

2

Делегат - это ссылка на метод с определенным списком параметров и типом возвращаемого значения. Он может включать или не включать объект.

Лямбда-выражение - это форма анонимной функции.


2

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

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

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


2

Совершенно очевидно, что вопрос был задан как «в чем разница между лямбдами и анонимными делегатами?» Из всех ответов здесь только один человек понял это правильно - главное отличие состоит в том, что лямбды можно использовать для создания деревьев выражений, а также делегатов.

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


1

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

Лямбда происходит от идеи лямбда-исчисления Алонсо Чёрча в 1930-х годах. Это анонимный способ создания функций. Они становятся особенно полезными для составления функций

Поэтому, хотя некоторые могут сказать, что лямбда - это синтаксический сахар для делегатов, я бы сказал, что делегаты - это мост, облегчающий людям использование лямбда-выражений в С #.


1

Некоторые базовые здесь. «Делегат» - это фактически имя переменной, которая содержит ссылку на метод или лямбду.

Это анонимный метод -

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

Поскольку анонимный метод не имеет имени, нам нужен делегат, в котором мы можем назначить оба этих метода или выражения. Для Ex.

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, используя делегаты, делегаты anon и 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

Я предполагаю, что ваш вопрос касается С #, а не .NET, из-за двусмысленности вашего вопроса, поскольку .NET не обходится в одиночку - то есть без С # - понимания делегатов и лямбда-выражений.

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

R (*thefunctionpointer) ( T ) ;

typedef - это тип, thefunctionpointerкоторый является типом указателей на функцию, принимающую объект типа Tи возвращающую объект типа R. Вы бы использовали это так:

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

где thefunctionбудет функция, принимающая Tи возвращающая R.

В С # вы бы выбрали

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будет функция, принимающая 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- функция, принимающая в качестве аргумента и возвращающая 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 ("Нажмите"); } и вызвал асинхронно new System.Threading.Thread (() => Console.Write («Выполняется в потоке»)). Start ();
Стив Купер,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.