2013-07-17 12 views
10

Qualcuno può fornire una distinzione concisa tra il metodo anonimo e le espressioni lambda?
Metodi anonimi rispetto all'espressione lambda

Uso del metodo anonimo:

private void DoSomeWork() 
{ 
    if (textBox1.InvokeRequired) 
    { 
     //textBox1.Invoke((Action)(() => textBox1.Text = "test")); 
     textBox1.Invoke((Action)delegate { textBox1.Text = "test"; }); 
    } 
} 

è solo una normale espressione lambda di essere lanciato a un delegato fortemente tipizzato o non v'è più di esso sotto copertura.

Sono ben consapevole che un delegato fortemente tipizzato come seguire

UpdateTextDelegate mydelegate = new UpdateTextDelegate(MethodName) 

bastare come un parametro di tipo System.Delegate, ma l'idea di metodo anonimo è piuttosto nuovo per me.

+1

Hai guardato questo? e forse altre domande? http://stackoverflow.com/questions/6008097/what-are-anonymous-methods-in-c –

+0

capisco perfettamente cosa sia un metodo anonimo/espressione lambda e lì utilizzo, la mia domanda è che differiscono in alcun modo dall'anonimo delegati o sono proprio gli stessi – sarepta

+2

Beh, ho appena scritto un **** carico di testo, quindi spero che troverai la tua risposta lì, se non me lo faccia sapere :) –

risposta

24

Che cosa è un metodo anonimo? È davvero anonimo? ha un nome? Tutte buone domande, quindi cominciamo da loro e lavoriamo fino alle espressioni lambda mentre procediamo.

Quando si esegue questa operazione:

public void TestSomething() 
{ 
    Test(delegate { Debug.WriteLine("Test"); }); 
} 

Cosa succede in realtà?

Il compilatore decide prima di prendere il "corpo" del metodo, che è questo:

Debug.WriteLine("Test"); 

e separare che in un metodo.

Due domande al compilatore deve ora rispondere:

  1. Dove devo mettere il metodo?
  2. Come dovrebbe apparire la firma del metodo?

La seconda domanda è facilmente risposta. La parte delegate { risponde a questa domanda. Il metodo non accetta parametri (nulla tra delegate e {), e dal momento che non ci importa circa il suo nome (da qui la parte "anonimo"), si può dichiarare il metodo come tale:

public void SomeOddMethod() 
{ 
    Debug.WriteLine("Test"); 
} 

Ma perché fa tutto questo?

Diamo un'occhiata a ciò che un delegato, come ad esempio Action è.

Un delegato è, se per un momento trascuriamo il fatto che i delegati in.NET sono in realtà lista concatenata di multipla "delegati" singoli, un riferimento (puntatore) per due cose:

  1. un'istanza di un oggetto
  2. un metodo su tale istanza oggetto

Così, con che la conoscenza, il primo pezzo di codice potrebbe effettivamente essere riscritta come questo:

public void TestSomething() 
{ 
    Test(new Action(this.SomeOddMethod)); 
} 

private void SomeOddMethod() 
{ 
    Debug.WriteLine("Test"); 
} 

Ora, il problema di questo è che il comp iler non ha modo di sapere che cosa fa effettivamente Test con il delegato e dato che metà del delegato è un riferimento all'istanza su cui il metodo deve essere chiamato, this nell'esempio precedente, non sappiamo quanti dati saranno referenziati.

Ad esempio, considerare se il codice precedente faceva parte di un oggetto veramente enorme, ma un oggetto che vive solo temporaneamente. Considera anche che Test memorizzerebbe quel delegato da qualche parte che sarebbe vissuto per un lungo periodo di tempo. Quel "lungo tempo" si legherebbe anche alla vita di quell'enorme oggetto, mantenendo a lungo un riferimento anche a quello, probabilmente non buono.

Quindi il compilatore non si limita a creare un metodo, ma crea anche una classe per tenerlo. Questo risponde alla prima domanda, dove dovrei metterlo?.

Il codice di cui sopra può quindi essere riscritta come segue:

public void TestSomething() 
{ 
    var temp = new SomeClass; 
    Test(new Action(temp.SomeOddMethod)); 
} 

private class SomeClass 
{ 
    private void SomeOddMethod() 
    { 
     Debug.WriteLine("Test"); 
    } 
} 

Cioè, per questo esempio, ciò che un metodo anonimo è davvero tutto.

Le cose diventano un po 'più pelosa se iniziare a utilizzare variabili locali, si consideri questo esempio:

public void Test() 
{ 
    int x = 10; 
    Test(delegate { Debug.WriteLine("x=" + x); }); 
} 

Questo è ciò che accade sotto il cofano, o almeno qualcosa di molto vicino ad esso:

public void TestSomething() 
{ 
    var temp = new SomeClass; 
    temp.x = 10; 
    Test(new Action(temp.SomeOddMethod)); 
} 

private class SomeClass 
{ 
    public int x; 

    private void SomeOddMethod() 
    { 
     Debug.WriteLine("x=" + x); 
    } 
} 

Il compilatore crea una classe, alza tutte le variabili che il metodo richiede in quella classe e riscrive tutti gli accessi alle variabili locali per accedere ai campi sul tipo anonimo.

Il nome della classe, e il metodo, sono un po 'strano, chiediamo LINQPad cosa sarebbe:

void Main() 
{ 
    int x = 10; 
    Test(delegate { Debug.WriteLine("x=" + x); }); 
} 

public void Test(Action action) 
{ 
    action(); 
} 

Se chiedo LINQPad per emettere il IL (Intermediate Language) di questo programma, ottengo questo:

// var temp = new UserQuery+<>c__DisplayClass1(); 
IL_0000: newobj  UserQuery+<>c__DisplayClass1..ctor 
IL_0005: stloc.0  // CS$<>8__locals2 
IL_0006: ldloc.0  // CS$<>8__locals2 

// temp.x = 10; 
IL_0007: ldc.i4.s 0A 
IL_0009: stfld  UserQuery+<>c__DisplayClass1.x 

// var action = new Action(temp.<Main>b__0); 
IL_000E: ldarg.0  
IL_000F: ldloc.0  // CS$<>8__locals2 
IL_0010: ldftn  UserQuery+<>c__DisplayClass1.<Main>b__0 
IL_0016: newobj  System.Action..ctor 

// Test(action); 
IL_001B: call  UserQuery.Test 

Test: 
IL_0000: ldarg.1  
IL_0001: callvirt System.Action.Invoke 
IL_0006: ret   

<>c__DisplayClass1.<Main>b__0: 
IL_0000: ldstr  "x=" 
IL_0005: ldarg.0  
IL_0006: ldfld  UserQuery+<>c__DisplayClass1.x 
IL_000B: box   System.Int32 
IL_0010: call  System.String.Concat 
IL_0015: call  System.Diagnostics.Debug.WriteLine 
IL_001A: ret   

<>c__DisplayClass1..ctor: 
IL_0000: ldarg.0  
IL_0001: call  System.Object..ctor 
IL_0006: ret   

Qui si può vedere che il nome della classe è UserQuery+<>c__DisplayClass1, e il nome del metodo è <Main>b__0. Ho modificato il codice C# che ha prodotto questo codice, LINQPad non produce nient'altro che IL nell'esempio sopra.

I segni di minore o maggiore sono lì per garantire che non sia possibile per caso creare un tipo e/o un metodo che corrisponda a ciò che il compilatore ha prodotto per te.

Quindi questo è fondamentalmente quello che è un metodo anonimo.

Quindi cos'è questo?

Test(() => Debug.WriteLine("Test")); 

Bene, in questo caso è lo stesso, è una scorciatoia per produrre un metodo anonimo.

È possibile scrivere in due modi:

() => { ... code here ... } 
() => ... single expression here ... 

Nella sua prima forma è possibile scrivere tutto il codice si farebbe in un normale corpo del metodo. Nella sua seconda forma ti è permesso di scrivere una espressione o una dichiarazione.

Tuttavia, in questo caso il compilatore tratterà questo:

() => ... 

allo stesso modo questo:

delegate { ... } 

Sono ancora i metodi anonimi, è solo che la sintassi () => è una scorciatoia per arrivare ad esso.

Quindi, se si tratta di una scorciatoia per arrivarci, perché ce l'abbiamo?

Bene, rende la vita un po 'più facile ai fini della quale è stata aggiunta, che è LINQ.

considerare questa dichiarazione LINQ:

var customers = from customer in db.Customers 
       where customer.Name == "ACME" 
       select customer.Address; 

Questo codice viene riscritta come segue:

var customers = 
    db.Customers 
     .Where(customer => customer.Name == "ACME") 
     .Select(customer => customer.Address"); 

Se si sceglie di usare la sintassi delegate { ... }, si dovrà riscrivere le espressioni con return ... e così e sembrerebbero più funky. La sintassi lambda è stata quindi aggiunta per semplificare la vita di noi programmatori durante la scrittura di codice come sopra.

Quindi quali sono le espressioni?

Finora non ho mostrato come Test è stato definito, ma cerchiamo di definire Test per il codice precedente:

public void Test(Action action) 

Questo dovrebbe essere sufficiente. Dice che "Ho bisogno di un delegato, è di tipo Action (senza parametri, non restituisce valori)".

Tuttavia, Microsoft ha anche aggiunto un modo diverso per definire questo metodo:

public void Test(Expression<Func<....>> expr) 

Si noti che ho lasciato cadere una parte lì, la parte ...., torniamo a quella .

Questo codice, abbinato a questa chiamata:

Test(() => x + 10); 

non sarà effettivamente passare un delegato, né qualcosa che può essere chiamato (immediatamente).Al contrario, il compilatore riscrivere questo codice a qualcosa simili (ma non a tutti come) il codice qui sotto:

var operand1 = new VariableReferenceOperand("x"); 
var operand2 = new ConstantOperand(10); 
var expression = new AdditionOperator(operand1, operand2); 
Test(expression); 

fondamentalmente il compilatore costruire un oggetto Expression<Func<...>>, contenente i riferimenti alle variabili, i valori letterali , gli operatori hanno usato, ecc. e passano l'albero degli oggetti al metodo.

Perché?

Bene, considera la parte db.Customers.Where(...) in alto.

Non sarebbe bello se, invece di scaricare tutti i clienti (e tutti i loro dati) dal database al client, collegandoli tutti, scoprendo quale cliente ha il nome giusto, ecc. Il codice in realtà chiedi al database di trovare quel singolo, corretto, cliente in una volta?

Questo è lo scopo dell'espressione. Entity Framework, Linq2SQL o qualsiasi altro livello di database di supporto LINQ, prenderà quell'espressione, la analizzerà, la scomporrà e scriverà un SQL correttamente formattato da eseguire sul database.

Questo potrebbe mai fare se ancora lo diamo delegati ai metodi che contengono IL. Si può farlo solo a causa di un paio di cose:

  1. La sintassi consentito in un'espressione lambda adatto per un Expression<Func<...>> è limitata (senza dichiarazioni, ecc)
  2. La sintassi lambda senza le parentesi graffe, che dice al compilatore che si tratta di una forma più semplice di codice

Quindi, cerchiamo di riassumere:

  1. I metodi anonimi in realtà non sono così anonimi, finiscono con un nome, con un metodo chiamato, solo non è necessario nominarli da soli
  2. È un sacco di magia del compilatore sotto il cofano che muove le cose intorno in modo che non c'è bisogno di
  3. Espressioni e delegati sono due modi di guardare alcune delle stesse cose
  4. le espressioni sono pensati per quadri che vuole conoscere ciò che il codice fa e come, in modo che essi utilizzare tale conoscenza per ottimizzare il processo (come scrivere una dichiarazione SQL)
  5. I delegati sono pensati per quadri che sono solo preoccupati per essere in grado di chiamare il metodo

Note:

  1. La parte .... per una semplice espressione quali è destinato per il tipo di valore di ritorno che si ottiene dall'espressione. Il () => ... simple expression ... consente solo le espressioni , ovvero qualcosa che restituisce un valore e non può essere più istruzioni.Come tale, un tipo di espressione valido è questo: Expression<Func<int>>, in pratica, l'espressione è una funzione (metodo) che restituisce un valore intero.

    Si noti che "l'espressione che restituisce un valore" è un limite per i parametri o tipi Expression<...> ma non per i delegati. Ciò è del tutto codice legale se il tipo di parametro di Test è un Action:

    Test(() => Debug.WriteLine("Test")); 
    

    Ovviamente, Debug.WriteLine("Test") non restituisce nulla, ma questo è legale. Se il metodo Test richiedeva un'espressione , tuttavia, non lo sarebbe, poiché un'espressione deve restituire un valore.

+2

Il termine "anonimo" è in realtà un ottimo termine per questo. Considera se qualcuno dona un sacco di soldi a una buona causa. Ovviamente quella persona ha un nome, non è noto alle persone che ottengono i soldi. In questo caso, il nome del metodo non ti è noto, ma in realtà ha un nome. –

+0

Discorso eccellente, ma c'è una sottile differenza al di là dello zucchero sintattico quando si cerca di trattare queste espressioni come espressioni. Cosa succede se si tenta di passare un delegato anonimo in un metodo che richiede solo un 'espressione > '? –

+0

Non puoi farlo. Si ottiene un errore del compilatore "Un'espressione di metodo anonima non può essere convertita in un albero di espressioni". Questo era da 'Test (delegate {return 10;});' quando il metodo è stato dichiarato come 'public void Test (Expression > expr)'. Nota, non sto dicendo che non ci sono cose che non ho toccato, quindi se riesci a trovarne sarei lieto di ampliarle, ma passare un delegato a un metodo che richiede un'espressione è un no- partire. –

1

Per essere precisi, ciò che si chiama "delegato anonimo" è in realtà un metodo anonimo.

Bene, sia lambdas che metodi anonimi sono solo zucchero di sintassi. Il compilatore genererà almeno un metodo 'normale' per te, anche se a volte (nel caso di una chiusura) genererà una classe nidificata con il metodo non più anonimo in essa.

5

C'è una sottile differenza da tenere presente. Considerare le seguenti query (utilizzando il proverbiale NorthWind).

Customers.Where(delegate(Customers c) { return c.City == "London";}); 
Customers.Where(c => c.City == "London"); 

Il primo utilizza un delegato anonimo e il secondo utilizza un'espressione lambda. Se valuti i risultati di entrambi vedrai la stessa cosa. Tuttavia, guardando l'SQL generato, vedremo una storia abbastanza diversa. La prima genera

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] 
FROM [Customers] AS [t0] 

considerando che il secondo genera

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] 
FROM [Customers] AS [t0] 
WHERE [t0].[City] = @p0 

Avviso nel primo caso, in cui la clausola non viene passato al database. Perchè è questo? Il compilatore è in grado di determinare che l'espressione lambda è una semplice espressione a riga singola che può essere mantenuta come un albero di espressioni, mentre il delegato anonimo non è un'espressione lambda e quindi non può essere impacchettato come Expression<Func<T>>. Come risultato nel primo caso, la migliore corrispondenza per il metodo di estensione Where è quella che estende IEnumerable piuttosto che la versione IQueryable che richiede uno Expression<Func<T, bool>>.

A questo punto, è poco utile per il delegato anonimo. È più prolisso e meno flessibile. In generale, raccomanderei sempre di utilizzare la sintassi lambda sulla sintassi dei delegati anonimi e di rilevare anche la parsimonia e la sintassi.

Problemi correlati