2012-05-08 15 views
63

Sono confuso circa l'ambito della variabile lambda, pensiamo ad esempio il seguenteQual è lo scopo di una variabile lambda in C#?

var query = 
    from customer in clist 
    from order in olist 
    .Where(o => o.CustomerID == customer.CustomerID && o.OrderDate == // line 1 
     olist.Where(o1 => o1.CustomerID == customer.CustomerID)  // line 2 
      .Max(o1 => o1.OrderDate)         // line 3 
    ) 
    select new { 
     customer.CustomerID, 
     customer.Name, 
     customer.Address, 
     order.Product, 
     order.OrderDate 
    }; 

In linea 1 Ho dichiarare una variabile lambda 'o' che significa che non può dichiarare nuovamente nella linea 2 (o almeno il compilatore si lamenta se provo a) Ma non si lamenta della riga 3 anche se 'o1' esiste già ??

Qual è lo scopo di una variabile lambda?

+10

Hai la risposta qui sotto, vorrei solo condividere un suggerimento semplice e pratico: quando l'ambito non è chiaro dalla lettura del codice, e quando lo scopo è importante, o evitare l'ambiguità visiva usando diversi nomi di variabili; o scrivi i tuoi lambda in forma estesa, usando le parentesi graffe, facendole sembrare più simili a funzioni - la mente e gli occhi della maggior parte dei programmatori fanno una distinzione più chiara quando vedi le tradizionali parentesi graffe. –

risposta

170

Le staffe dare l'indizio - la variabile lambda viene catturato nel campo di applicazione dove è dichiarato:

.Where(o => ... olist.Where(o1 => ...).Max(o1 => ...)) 
    // |----------------------------------------------| scope of o 
    //      |---------|     scope of first o1 
    //          |---------| scope of second o1 

Nota che non c'è alcuna sovrapposizione per i due o1 variabili, ma entrambi si sovrappongono (o ombra) il o variabile e quindi non può usare lo stesso nome.

+58

+1 per la visualizzazione – Cheezmeister

+2

ottima risposta ... grazie amico! – mfc

+3

Risposta migliore che ho visto su SO da 3 anni. – kizzx2

4

becasue Lamda è la sostituzione della funzione anonima qui in voi codice

stessa portata

Where(o => o.CustomerID == customer.CustomerID && o.OrderDate == //line 1 
     olist.Where(o1 => o1.CustomerID == customer.CustomerID) //line 2  

è portata della funzione in cui varialble "o" live

qui in linea a tre questo è un nuovo scrop della variabile, cioè la nuova funzione scope

Differente Scope

.Max(o1 => o1.OrderDate) )  //line 3 

modo che sia la Reson in line1 e line2 varialbe "o" definito in Riga1 non definito riga2 causa della stessa scrop e "o1" definire in riga2 può essere definito nuovamente in line3 becauase esso è in funzioni diverse

14

L'ambito di un parametro lambda è uguale all'intero ambito del corpo dell'espressione lambda, comprese eventuali espressioni lambda interne o ambiti.

Se espandiamo la sintassi delle vostre espressioni lambda e aggiungere un po indentazione amichevole può diventare più chiaro (anche se probabilmente nulla chiaro come yamen's diagrammatic answer!):

.Where(o => { 
    return o.CustomerID == customer.CustomerID 
     && o.OrderDate == olist.Where(
      o1 => o1.CustomerID == customer.CustomerID 
     ) 
     .Max(
      o1 => o1.OrderDate 
     ); 
}) 

notate che il vostro .Where().Max() chiamata si trova all'interno di un esterno .Where(). Il o nel lambda esterno è incapsulato dal lambda esterno all'interno del tuo lambda interno (questo è chiamato chiusura) tale che esiste già nell'ambito dei tuoi lambda interni e non può essere riutilizzato come parametro.

È possibile riutilizzare o1 perché i due lambda interni sono completamente separati l'uno dall'altro, quindi non vivono oltre l'ambito di uno dei due.

2

C# non supporta lo shadowing in questo modo.

Il motivo o1 funziona di nuovo, è che non ombreggia il precedente o1.

5

Non è possibile utilizzare lo stesso nome di variabile in due ambiti se uno degli ambiti contiene l'altro.

Nella sua interrogazione, o viene introdotto nel perimetro esterno, in modo che non può essere utilizzato di nuovo nella seconda Where() o in Max(), perché questi scopi sono contenute in quello esterno.

D'altra parte, è possibile utilizzare o1 in entrambi gli ambiti interni perché uno non contiene l'altro, quindi non vi è alcuna ambiguità lì.

2

È uguale a qualsiasi altra variabile. L'ambito di o è l'intera espressione nel tuo primo Where, quindi non puoi riutilizzarlo nel secondo, che è all'interno del primo. Ma l'ambito di o1 è solo l'espressione nel tuo secondo Where, quindi puoi utilizzarlo nell'espressione del tuo Max, che è al di fuori del secondo Where. Nel codice:

// o scope lasts until the first bracket is closed 
Where(o => o.CustomerID == customer.CustomerID && o.OrderDate == 
// o1 scope lasts until the second bracket is closed; the first is not yet closed here 
     olist.Where(o1 => o1.CustomerID == customer.CustomerID) 
// The second bracket is closed, so o1 is already out of scope; o is still in scope 
      .Max(o1 => o1.OrderDate) 
) 
// The first bracket is closed, so o is finally out of scope 
2

provo immagine in questo modo secondo il vostro codice di ...

.Where(o => o.CustomerID == customer.CustomerID && o.OrderDate == // line 1 
     olist.Where(o1 => o1.CustomerID == customer.CustomerID)  // line 2 
      .Max(o1 => o1.OrderDate)         // line 3 
    ) 

modo piuttosto grezzo di spiegare ma i vostri parentesi determinare la portata. Il tuo secondo o1 non è nidificato all'interno del secondo in cui, l'altro saggio avresti lo stesso problema

//outermost where 

((BEGIN-o 

//inner where 

(BEGIN-o1 END-o1) 

//max 

(BEGIN-o1 END-o1) 

END-o)) 
1
var external = 1; 

//This is a scope 
Action scope = new Action(() => 
{ 
    //myVar is not accessible from outside 
    var myVar = 0 + external; 
    Console.WriteLine(myVar); //outputs 1 
}); 

//Call the scope 
scope(); 
//Console.WriteLine(myVar);//Will not compile 

Quando il codice viene compilato, tutta la logica dal vuoto dichiarato nell'azione ()=>{ ... } sarà spostato su un metodo sul tipo con un nome storpiato.

Il tempo di esecuzione chiamerà la funzione appena creata quando raggiunge quel punto sulla pila.

È possibile passare valori in un ambito/lambda in vari modi, che è lo stesso per farli uscire.

Le variabili dichiarate in una lambda non sono accessibili all'esterno con il loro nome dichiarato.

È anche possibile utilizzare la riflessione per estrarre il nome storpiato, tuttavia non sono sicuro che sia necessario. (Per favore fatemi sapere se ho torto.)

+0

La mia risposta è più semplice da leggere e più generale allo scopo di rispondere alla domanda: gli altri non mostrano l'ambito o non riesci a ripetere i nomi in un ambito, ad esempio (o => o.Somethings.Where (o => o.IsTrue)) // Errore perché o già in uso. Merito più rappresentanti. – Jay