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:
- Dove devo mettere il metodo?
- 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:
- un'istanza di un oggetto
- 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:
- La sintassi consentito in un'espressione lambda adatto per un
Expression<Func<...>>
è limitata (senza dichiarazioni, ecc)
- 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:
- I metodi anonimi in realtà non sono così anonimi, finiscono con un nome, con un metodo chiamato, solo non è necessario nominarli da soli
- È un sacco di magia del compilatore sotto il cofano che muove le cose intorno in modo che non c'è bisogno di
- Espressioni e delegati sono due modi di guardare alcune delle stesse cose
- 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)
- I delegati sono pensati per quadri che sono solo preoccupati per essere in grado di chiamare il metodo
Note:
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.
Hai guardato questo? e forse altre domande? http://stackoverflow.com/questions/6008097/what-are-anonymous-methods-in-c –
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
Beh, ho appena scritto un **** carico di testo, quindi spero che troverai la tua risposta lì, se non me lo faccia sapere :) –