2012-05-09 20 views
5

Qual è l'ambito della variabile locale dichiarato in Linq Query.ambito variabile locale in metodo linq anonimo (chiusura)

stavo scrivendo seguente codice

static void Evaluate() 
    { 
     var listNumbers = Enumerable.Range(1, 10).Select(i => i); 
     int i = 10; 
    } 

compilatore errore segnalato on line int i = 10, affermando

A local variable named 'i' cannot be declared in this scope because it would give a different meaning to 'i', which is already used in a 'child' scope to denote something else 

Non riesco a capire perché questo errore è in arrivo.

La mia comprensione è che i diventerà fuori campo dopo la prima riga (nel ciclo foreach). Quindi, i può essere dichiarato di nuovo.

Il comportamento effettivo è che non è possibile accedere a i dopo la prima riga (nel ciclo foreach), che è corretto. Ma i non può essere dichiarato di nuovo. Questo sembra strano.

EDIT Questa è una domanda seguente basata sulla risposta di Andras. La risposta è molto buona, ma causa ulteriori dubbi.

static void Evaluate3() 
    { 
     var listNumbers = Enumerable.Range(1, 10).Select(i => i); 
     var listNumbers1 = Enumerable.Range(1, 10).Select(i => i); 
    } 

Sulla base della logica della funzione Valutare che .Select (i => i), e int i = 10, entrambi i, sono locali operato blocco e quindi errori complicazione.

Funzione Evaluate3 non dovrebbe compilare così come ci sono due nel blocco del metodo, ma si sta compilando correttamente senza alcun avviso/errore.

Domanda: Sia Evaluate che Evaluate3 non devono essere compilati, o entrambi devono essere compilati.

+0

Sei sicuro che sia un problema con l'istruzione linq? Se lo rifai come '.Seleziona (x => x)' l'errore scompare davvero? –

+0

questo problema non è correlato a LINQ. è correlato all'espressione lamda all'interno di linq. .Seleziona (x => x) -> si l'errore va via – Tilak

+0

Usiamo linq in modo esteso e non mi è mai capitato di attraversarlo. Ho provato e ho la stessa lamentela. Quello che considero più strano è che posso avere 'var listNumbers = Enumerable.Range (1, 10) .Select (i => i); var listNumbers2 = listNumbers.Cast () .Seleziona (i => i); 'e questo non genera un errore anche se sto usando lo stesso nome di variabile, una volta come numero intero e una volta come stringa. Sembra che il secondo uso di 'i' dovrebbe causare anche un errore ... –

risposta

7

Il fatto chiave da notare qui è che una dichiarazione:

int i; 

... ha effetto su tutto il perimetro che racchiude da dall'inizio alla fine - non solo dal punto in cui è dichiarata. In .Net la dichiarazione di una variabile locale è solo un'istruzione al compilatore per riservare quel nome e locale per l'intero ambito. Ciò significa che una volta dichiarato, è già riservato per tutte le linee prima e dopo.

In effetti, significa che si dovrebbe effettivamente letto Evaluate come:

static void Evaluate() 
{ 
    int i; 
    var listNumbers = Enumerable.Range(1, 10).Select(i => i); 
    i = 10; 
} 

E se lo fate scrivere il vostro metodo di conseguenza, vedrete che l'errore del compilatore si verifica sulla dichiarazione lambda invece - che è perfettamente ragionevole. Per fortuna, il compilatore C# è abbastanza intelligente, dal punto di vista umano, per riconoscere che l'ordinamento del codice è importante per us e in realtà assegna l'errore del compilatore a qualsiasi riga sorgente sia la seconda o successiva dichiarazione; quindi perché nella tua versione di Evaluate avviene sulla riga int i = 10;. Con questa conoscenza della durata effettiva della funzione locale i, il compilatore è corretto: l'uso di i è in conflitto con l'uso precedente di i nella lambda.

È possibile utilizzare scoping esplicito per evitare questo:

static void Evaluate() 
{ 
    var listNumbers = Enumerable.Range(1, 10).Select(i => i); 
    { 
    int i = 10; 
    } 
} 

Nel caso di Evaluate3 è sufficiente notare che, mentre entrambe le lambda condividono la portata funzione di genitore, hanno anche loro, ed è lì che la loro i s sono dichiarati - ed è per questo che non interferiscono l'uno con l'altro (sono, in effetti, gli ambiti di pari livello).

inciso Evaluate e Evaluate3 può in definitiva essere semplificata a questo:

static void Evaluate() 
{ 
    { 
    int i; 
    } 
    int i; //<-- error 
} 

static void Evaluate3() 
{ 
    { 
    int i; 
    } 
    { 
    int i; 
    } 
    //fine here - both i's are in different scopes. 
} 

Ed è in realtà il secondo scenario qui che ho usato scoping esplicita per prima - cioè in ambiti diversi all'interno della stessa funzione, dove il i ha in realtà un tipo diverso in ciascuno. Come ho detto - Non l'ho mai più fatto e il codice in questione non è più in diretta :)

+0

se vedi dal punto di vista dell'errore di codifica, non lo farò mai più. E 'solo che ho affrontato, e avevo la mente vuota su questo. – Tilak

+0

grazie per la risposta, ho posto una domanda di follow-up basata sulla risposta. – Tilak

+0

ho capito il comportamento e la spiegazione. La mia opinione è che non è l'ambito del blocco del metodo padre (Valuta nel primo codice, Evaluate3 sul secondo codice) che sta causando il problema. È l'ambito all'interno del metodo Anonimo che causa il problema. Poiché il metodo anonimo può accedere alle variabili locali della funzione esterna, In Valuta, causa errore mentre sta vedendo due i. In Evaluate3, poiché nessuna espressione lamda sta vedendo due i, nessun avvertimento/errore è contrassegnato. Voglio sentire la tua opinione. Se sei d'accordo, ti preghiamo di aggiornare la risposta con una parola migliore. Non sono così espressivo come te. – Tilak

1

Da the spec:

The scope of a local variable declared in a local-variable-declaration is the block in which the declaration occurs. It is an error to refer to a local variable in a textual position that precedes the local-variable-declarator of the local variable. 

tuo lambda i => i è all'interno del campo di applicazione della variabile locale int i = 10 anche se si dichiara in anticipo. Piuttosto che lanciare un errore perché stai usando i prima che venga dichiarato, il compilatore ti sta facendo notare che hai già usato i per riferirti a qualcos'altro e che questa dichiarazione lo avrebbe cambiato.

EDIT: Dopo l'aggiornamento:

Nel primo caso, il vostro primo i è racchiuso all'interno del lambda, ma il secondo i abbraccia l'intera Evaluate metodo incluso il lambda - da qui si ottiene l'errore.

Nel secondo caso, il vostro primo i è racchiuso nel suo lambda, e il secondo i è racchiuso all'interno sua lambda - né i è nell'ambito di applicazione degli altri, quindi non c'è nessun errore.

Il paragrafo "Basato sulla logica di ... both i, sono locale per il blocco funzione ..." non è corretto - il primo i non è locale al blocco funzione ma al lambda.

+0

Sembra comunque un comportamento strano. Il primo 'i' è usato solo all'interno dell'istruzione' Select', quindi l'ipotesi facile sarebbe che sia locale solo all'istruzione 'Select', non all'intero metodo' Evaluate() '. –

+0

@Grant Il primo 'i' è effettivamente" locale all'istruzione 'Select'". Tuttavia, il secondo 'i' è locale per l'intero metodo' Evaluate() ', incluso il lambda. – Rawling