Jaka jest różnica między lambdami a delegatami w .NET Framework?

86

Często zadawano mi to pytanie i pomyślałem, że poproszę o wskazówki, jak najlepiej opisać różnicę.

ScottKoon
źródło
2
Czy przez „delegatów” masz na myśli typy pełnomocników, czy anonimowych delegatów? Są też inni.
Chris Ammerman,
1
dlaczego ludzie tak komplikują jego pytanie? Po prostu odpowiedz, czym jest delegat, a czym lambda. Podaj jak najwięcej wyjaśnień i pozwól mu wybrać to, co jest dla niego odpowiednie.
Imir Hoxha

Odpowiedzi:

96

W rzeczywistości są to dwie bardzo różne rzeczy. „Delegat” to w rzeczywistości nazwa zmiennej, która zawiera odniesienie do metody lub wyrażenia lambda, a lambda to metoda bez stałej nazwy.

Lambdy są bardzo podobne do innych metod, z wyjątkiem kilku subtelnych różnic.

  1. Normalna metoda jest zdefiniowana w „instrukcji” i powiązana ze stałą nazwą, podczas gdy lambda jest definiowana „w locie” w „wyrażeniu” i nie ma stałej nazwy.
  2. Niektórych lambd można używać z drzewami wyrażeń .NET, natomiast metod nie.

Delegat jest definiowany w następujący sposób:

delegate Int32 BinaryIntOp(Int32 x, Int32 y);

Zmienna typu BinaryIntOp może mieć przypisaną metodę lub labmda, o ile podpis jest taki sam: dwa argumenty Int32 i zwrot Int32.

Lambda można zdefiniować w ten sposób:

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

Inną rzeczą, na którą należy zwrócić uwagę, jest to, że chociaż ogólne typy Func i Action są często uważane za „typy lambda”, są one takie same jak inne delegaty. Zaletą jest to, że zasadniczo definiują nazwę dla dowolnego typu delegata, którego możesz potrzebować (do 4 parametrów, chociaż z pewnością możesz dodać więcej własnych). Jeśli więc używasz szerokiej gamy typów delegatów, ale nie więcej niż raz, możesz uniknąć zaśmiecania kodu deklaracjami delegatów przy użyciu funkcji Func i Action.

Oto ilustracja tego, jak Func i Action są „nie tylko dla lambd”:

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

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

Inną przydatną rzeczą jest wiedzieć, że typy delegatów (a nie same metody) z tą samą sygnaturą, ale różnymi nazwami nie będą do siebie rzutowane niejawnie. Obejmuje to delegatów Func i Action. Jeśli jednak podpis jest identyczny, możesz jawnie rzutować między nimi.

Idziemy o krok dalej ... W C # funkcje są elastyczne, z wykorzystaniem lambd i delegatów. Ale C # nie ma „funkcji pierwszej klasy”. Możesz użyć nazwy funkcji przypisanej do zmiennej delegata, aby zasadniczo utworzyć obiekt reprezentujący tę funkcję. Ale to naprawdę sztuczka kompilatora. Jeśli rozpoczniesz instrukcję od wpisania nazwy funkcji, po której następuje kropka (tj. Spróbujesz uzyskać dostęp do elementu członkowskiego samej funkcji), zobaczysz, że nie ma tam żadnych elementów członkowskich, do których można by się odwołać. Nawet te z Object. Zapobiega to wykonywaniu przez programistę pożytecznych (i potencjalnie niebezpiecznych) rzeczy, takich jak dodawanie metod rozszerzających, które można wywołać w dowolnej funkcji. Najlepsze, co możesz zrobić, to rozszerzyć samą klasę Delegate, która z pewnością jest również przydatna, ale nie aż tak bardzo.

Aktualizacja: Zobacz także odpowiedź Karga ilustrującą różnicę między anonimowymi delegatami a metodami i lambdami.

Aktualizacja 2: James Hart zwraca uwagę, że lambdy i delegaty nie są jednostkami .NET (tj. CLR nie ma pojęcia delegata ani lambdy), ale raczej są konstrukcjami ramowymi i językowymi.

Chris Ammerman
źródło
Dobre wytłumaczenie. Chociaż myślę, że masz na myśli „funkcje pierwszej klasy”, a nie „obiekty pierwszej klasy”. :)
ibz
1
Masz rację. Miałem inną strukturę zdania podczas pisania („Funkcje C # nie są tak naprawdę obiektami pierwszej klasy”) i zapomniałem to zmienić. Dzięki!
Chris Ammerman,
Normalna metoda jest zdefiniowana w „instrukcji” Instrukcja jest działaniem w sekwencji programu imperatywnego, prawdopodobnie opartą na wyrażeniu. Czy definicja metody nie jest inną strukturą gramatyczną? Definicja metody nie jest wymieniona w docs.microsoft.com/en-us/dotnet/csharp/tour-of-csharp/ ...
Max Barraclough
32

Pytanie jest trochę niejednoznaczne, co wyjaśnia dużą rozbieżność w otrzymywanych odpowiedziach.

Właściwie zapytałeś, jaka jest różnica między lambdami a delegatami w środowisku .NET; to może być jedna z wielu rzeczy. Czy ty pytasz:

  • Jaka jest różnica między wyrażeniami lambda a anonimowymi delegatami w języku C # (lub VB.NET)?

  • Jaka jest różnica między obiektami System.Linq.Expressions.LambdaExpression a obiektami System.Delegate w .NET 3.5?

  • A może coś pomiędzy tymi skrajnościami lub wokół nich?

Wydaje się, że niektórzy ludzie próbują udzielić odpowiedzi na pytanie „jaka jest różnica między wyrażeniami Lambda w języku C # a .NET System.Delegate?”, Co nie ma większego sensu.

Platforma .NET sama w sobie nie rozumie koncepcji anonimowych delegatów, wyrażeń lambda ani domknięć - to wszystko jest zdefiniowane przez specyfikacje języka. Pomyśl o tym, jak kompilator C # tłumaczy definicję metody anonimowej na metodę w wygenerowanej klasie ze zmiennymi składowymi do przechowywania stanu zamknięcia; w .NET nie ma nic anonimowego w delegacie; jest po prostu anonimowy dla programisty C # piszącego go. To samo dotyczy wyrażenia lambda przypisanego do typu delegata.

Co .NET robi zrozumieć jest pomysł delegata - typ, który opisuje podpis metody, które reprezentują zarówno przypadki związane wywołań metod szczegółowych dotyczących poszczególnych obiektów lub rozmów niezwiązanych z konkretnym sposobem na określonego rodzaju, które mogą być stosowane w odniesieniu każdy obiekt tego typu, jeśli wspomniana metoda jest zgodna z wspomnianym podpisem. Wszystkie takie typy dziedziczą po System.Delegate.

NET 3.5 wprowadza również przestrzeń nazw System.Linq.Expressions, która zawiera klasy do opisywania wyrażeń kodu - i która w związku z tym może również reprezentować powiązane lub niezwiązane wywołania metod na określonych typach lub obiektach. Wystąpienia LambdaExpression można następnie skompilować do rzeczywistych delegatów (przy czym metoda dynamiczna oparta na strukturze wyrażenia jest kodowana i zwracany jest wskaźnik delegata).

W języku C # można tworzyć wystąpienia typów System.Expressions.Expression, przypisując wyrażenie lambda do zmiennej tego typu, co spowoduje wygenerowanie odpowiedniego kodu do skonstruowania wyrażenia w czasie wykonywania.

Oczywiście, jeśli zostały pytaniem, jaka jest różnica między wyrażeniami lambda i anonimowe metody w C #, po wszystkim, to wszystko to jest dość dużo irelevant, aw tym przypadku różnica jest zwięzłość, który skłania się ku anonimowych delegatów kiedy don” Nie przejmuj się parametrami i nie planuj zwracania wartości, a także lambd, gdy chcesz wpisać parametry wywnioskowane i typy zwracane.

Wyrażenia lambda obsługują generowanie wyrażeń.

James Hart
źródło
3
Świetne informacje! Zainspirowałeś mnie do odpalenia reflektora i spojrzenia na IL. Nie wiedziałem, że lambdy powodują wygenerowanie klas, ale teraz, kiedy o tym myślę, ma to sens.
Chris Ammerman,
20

Jedyną różnicą jest to, że anonimowy delegat może pomijać parametry, podczas gdy lambda musi pasować do dokładnego podpisu. Dany:

public delegate string TestDelegate(int i);

public void Test(TestDelegate d)
{}

możesz to wywołać na cztery sposoby (zwróć uwagę, że druga linia ma anonimowego delegata, który nie ma żadnych parametrów):

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

Nie można przekazać wyrażenia lambda, które nie ma parametrów ani metody, która nie ma parametrów. Nie są dozwolone:

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

private string D2()
{
    return String.Empty;
}
Karg
źródło
13

Delegaci są odpowiednikami wskaźników funkcji / wskaźników metod / wywołań zwrotnych (wybierz), a lambdy są w zasadzie uproszczonymi funkcjami anonimowymi. Tak przynajmniej mówię ludziom.

Dan Shield
źródło
Dokładnie! Nie ma różnicy". Są to dwie z natury różne rzeczy.
ibz
3

Nie mam z tym dużego doświadczenia, ale sposób, w jaki bym to opisał, jest taki, że delegat jest opakowaniem wokół dowolnej funkcji, podczas gdy wyrażenie lambda samo w sobie jest funkcją anonimową.

chessguy
źródło
3

Delegat jest zawsze po prostu wskaźnikiem funkcji. Lambda może przekształcić się w delegata, ale może również przekształcić się w drzewo wyrażenia LINQ. Na przykład,

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

Pierwsza linia tworzy delegata, a druga tworzy drzewo wyrażeń.

Curt Hagenlocher
źródło
2
To prawda, ale różnica między nimi polega na tym, że są to dwie zupełnie różne koncepcje . To jak porównywanie jabłek i pomarańczy. Zobacz odpowiedź Dana Shielda.
ibz,
2

lambdy są po prostu cukrem syntaktycznym dla delegata. Kompilator kończy konwersję wyrażeń lambda na delegatów.

Sądzę, że są takie same:

Delegate delegate = x => "hi!";
Delegate delegate = delegate(object x) { return "hi";};
Gilligan
źródło
2
żaden z tych przykładów nie jest kompilowany. Nawet jeśli zmienisz nazwę wystąpienia Delegatez „delegat”, które jest słowem kluczowym.
Steve Cooper,
2

Delegat to podpis funkcji; coś jak

delegate string MyDelegate(int param1);

Delegat nie implementuje treści.

Lambda to wywołanie funkcji, które pasuje do podpisu delegata. W przypadku powyższego delegata możesz użyć dowolnego z;

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

Jednak Delegatetyp jest źle nazwany; tworzenie obiektu typu w Delegaterzeczywistości tworzy zmienną, która może przechowywać funkcje - czy to lambdy, metody statyczne, czy metody klas.

Steve Cooper
źródło
Kiedy tworzysz zmienną typu MyDelegate, tak naprawdę nie jest to typ środowiska uruchomieniowego. Typ środowiska wykonawczego to Delegat. Istnieją sztuczki kompilatora związane z kompilacją delegatów, wyrażeń lambda i drzew wyrażeń, które, jak sądzę, powodują, że kod sugeruje rzeczy, które nie są prawdziwe.
Chris Ammerman,
2

Delegat to odwołanie do metody z określoną listą parametrów i zwracanym typem. Może zawierać obiekt lub nie.

Wyrażenie lambda jest formą funkcji anonimowej.

Peter Ritchie
źródło
2

Delegat to kolejka wskaźników funkcji, wywołanie delegata może wywołać wiele metod. Lambda jest zasadniczo anonimową deklaracją metody, która może być różnie interpretowana przez kompilator, w zależności od kontekstu, w jakim jest używana.

Możesz pobrać delegata, który wskazuje na wyrażenie lambda jako metodę, rzutując je na delegata lub przekazując go jako parametr do metody, która oczekuje określonego typu delegata, kompilator rzuci go za Ciebie. Używając go wewnątrz instrukcji LINQ, lambda zostanie przetłumaczona przez kompilator na drzewo wyrażeń, a nie po prostu delegata.

Różnica polega na tym, że lambda to zwięzły sposób definiowania metody wewnątrz innego wyrażenia, podczas gdy delegat jest rzeczywistym typem obiektu.

justin.m.chase
źródło
2

Jest całkiem jasne, że pytanie miało być „Jaka jest różnica między lambdas i anonimowych delegatami?" Spośród wszystkich odpowiedzi tylko jedna osoba zrozumiała to dobrze - główna różnica polega na tym, że lambdy mogą być używane zarówno do tworzenia drzew wyrażeń, jak i delegatów.

Możesz przeczytać więcej na MSDN: http://msdn.microsoft.com/en-us/library/bb397687.aspx

Philip Beber
źródło
1

Delegaci są tak naprawdę tylko strukturalnym typowaniem funkcji. Możesz zrobić to samo przy nominalnym wpisywaniu i implementowaniu anonimowej klasy, która implementuje interfejs lub klasę abstrakcyjną, ale kończy się to dużą ilością kodu, gdy potrzebna jest tylko jedna funkcja.

Lambda wywodzi się z idei rachunku lambda Alonzo Churcha w latach trzydziestych XX wieku. Jest to anonimowy sposób tworzenia funkcji. Stają się szczególnie przydatne do tworzenia funkcji

Więc chociaż niektórzy mogą powiedzieć, że lambda jest cukrem syntaktycznym dla delegatów, powiedziałbym, że delegaci są pomostem ułatwiającym ludziom przejście na lambdy w języku c #.

Steve g
źródło
1

Tutaj trochę podstawowych. „Delegat” to w rzeczywistości nazwa zmiennej, która zawiera odniesienie do metody lub wyrażenia lambda

To jest metoda anonimowa -

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

Ponieważ metoda anonimowa nie ma żadnej nazwy, potrzebujemy delegata, w którym możemy przypisać obie te metody lub wyrażenie. Dla Ex.

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

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

To samo z wyrażeniem lambda. Zwykle do ich używania potrzebujemy pełnomocnika

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

Możemy użyć delegata func, aby użyć tego wyrażenia.

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

bool result = checkStudentAge ( Student Object);
Yogesh Prajapati
źródło
0

Lambdy to uproszczone wersje delegatów. Mają niektóre właściwości zamknięcia, takie jak anonimowi delegaci, ale pozwalają również na użycie niejawnego wpisywania. Taka lambda:

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

jest o wiele bardziej zwięzłe niż to, co możesz zrobić z delegatem:

something.Sort(sortMethod);
...

private int sortMethod(SomeType one, SomeType two)
{
    one.CompareTo(two)
}
Michael Meadows
źródło
Masz na myśli, że lambdy są jak uproszczone metody anonimowe (nie są delegowane). Podobnie jak metody (anonimowe lub nie), można je przypisać do zmiennej delegowanej.
Lucas
0

Oto przykład, który chwilę zamieściłem na moim kiepskim blogu. Powiedzmy, że chcesz zaktualizować etykietę z wątku roboczego. Mam 4 przykłady, jak zaktualizować tę etykietę od 1 do 50 za pomocą delegatów, delegatów anon i 2 typów lambd.

 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); 
         } 
     }
Echostorm
źródło
0

Zakładam, że Twoje pytanie dotyczy języka C # a nie .NET, ze względu na niejednoznaczność pytania, ponieważ .NET nie radzi sobie samodzielnie - czyli bez C # - rozumienia delegatów i wyrażeń lambda.

Delegat ( normalny , w przeciwieństwie do tzw. Delegatów generycznych , patrz później) delegat powinien być postrzegany jako rodzaj c ++ typedeftypu wskaźnika funkcji, na przykład w języku c ++:

R (*thefunctionpointer) ( T ) ;

typedef to typ thefunctionpointerbędący typem wskaźników do funkcji pobierającej obiekt typu Ti zwracającej obiekt typu R. Używałbyś tego w ten sposób:

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

gdzie thefunctionbyłaby funkcja przyjmująca a Ti zwracająca R.

W c # poszedłbyś na

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

i użyłbyś tego w ten sposób:

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

gdzie thefunctionbyłaby funkcja przyjmująca a Ti zwracająca R. Dotyczy to delegatów, tzw. Zwykłych delegatów.

Teraz masz również delegatów ogólnych w języku C #, którzy są delegatami, które są ogólne, tj. Są „szablonowe”, że tak powiem, przy użyciu wyrażenia c ++. Są zdefiniowane w następujący sposób:

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

Możesz ich używać w ten sposób:

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

gdzie thefunction2jest funkcją przyjmującą jako argument i zwracającą double.

Teraz wyobraź sobie, że zamiast tego thefunction2chciałbym użyć „funkcji”, która na razie nie jest nigdzie zdefiniowana, za pomocą instrukcji i której nigdy nie będę używać później. Następnie c # pozwala nam użyć wyrażenia tej funkcji. Przez ekspresję mam na myśli „matematyczny” (lub funkcjonalny, aby trzymać się programów) ekspresję nim, na przykład: Do double xbędę kojarzyćdouble x*x . W matematyce piszesz to za pomocą lateksowego symbolu „\ mapsto” . W języku C # notacja funkcjonalna została zapożyczona: =>. Na przykład :

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

(double x) => x * xjest wyrażeniem . To nie jest typ, podczas gdy delegaci (ogólne lub nie) są.

Moralność? Na koniec, czym jest delegat (względnie delegat ogólny), jeśli nie typem wskaźnika funkcji (odpowiednio opakowany + inteligentny + ogólny typ wskaźnika funkcji), co? Coś innego ! Zobacz to i tamto .

ujsgeyrr1f0d0d0r0h1h0j0j_juj
źródło
-1

Cóż, naprawdę uproszczona wersja jest taka, że ​​lambda jest po prostu skrótem dla funkcji anonimowej. Pełnomocnik może wykonywać znacznie więcej niż tylko funkcje anonimowe: takie rzeczy, jak zdarzenia, wywołania asynchroniczne i wiele łańcuchów metod.

Joel Coehoorn
źródło
1
lambda mogą służyć jako programy obsługi zdarzeń; button.Click + = (nadawca, eventArgs) => {MessageBox.Show ("Kliknij"); } i wywołał asynchronicznie nowy System.Threading.Thread (() => Console.Write ("Wykonano w wątku")). Start ();
Steve Cooper,