2015-04-20 19 views
21

Ho un'immaginazione debole per quanto riguarda i nomi, quindi spesso mi ritrovo a riutilizzare gli identificatori nel mio codice. Questo mi ha fatto incontrare questo specifico problema.Parametro Lambda in conflitto con il campo classe sul campo di accesso in ambito successivo

Ecco qualche esempio di codice:

public delegate void TestDelegate(int test); 

public class Test 
{ 
    private int test; 

    private void method(int aaa) 
    { 
     TestDelegate del = test => aaa++; 

     test++; 
    } 

    public static void Main() 
    { 
    } 
} 

Qui ci sono gli errori di compilazione (in uscita da ideone):

prog.cs(11,3): error CS0135: `test' conflicts with a declaration in a child block 
prog.cs(9,22): (Location of the symbol related to previous error) 
Compilation failed: 1 error(s), 0 warnings 

Linea 11 contiene test++, linea 9 contiene la lambda.

Per inciso, Visual Studio 2013 dà un errore diverso:

'test' conflicts with the declaration 'Namespace.Test.test' 

L'errore si verifica presso l'incremento sulla linea 11 solo.

Il codice viene compilato correttamente se commento la riga 9 (la lambda) o la riga 11 (l'incremento).

Questo problema è una sorpresa per me - ero sicuro che i nomi dei parametri lambda possono entrare in conflitto solo con i nomi delle variabili di metodo locali (che è una sorta di conferma della compilazione del codice quando commento l'incremento). Inoltre, come può il parametro lambda influenzare l'incremento, che è proprio al di fuori dell'ambito del lambda?

Non riesco a capire come funziona ... Che cosa ho fatto di sbagliato? E cosa significano i messaggi di errore criptico in questo caso?

EDIT dopo tutte le grandi risposte:

Quindi penso ho finalmente capito la regola che mi sono rotto. Non è ben scritto nelle specifiche C# (7.6.2.1, vedere la risposta di Jon Skeet per la citazione). Ciò che è stato supposto significare è qualcosa di simile:

è possibile Non utilizzare lo stesso identificatore di fare riferimento a cose diverse (entità) nello stesso "spazio locale dichiarazione della variabile" se uno dei offendere usa è (direttamente) situato in un ambito che può essere "visto" dall'ambito in cui l'altro è (direttamente) situato.

Non lo standard del fraseggio standard, ma spero che tu abbia capito cosa intendo. Questa regola avrebbe dovuto permettere questo:

{ 
    int a; 
} 

{ 
    int a; 
} 

perché nessuno degli scopi delle due variabili a possono essere "visti" dal campo di applicazione di altri;

e disattivare questo:

{ 
    int a; 
} 

int a; 

perché la seconda dichiarazione variabile è "visto" dalla portata della prima variabile

e disattivare questo:

class Test 
{ 
    int test; 

    void method() 
    { 
     { 
      int test; 
     } 

     test++; 
    } 
} 

causa l'incremento del campo può essere "visto" dall'ambito del blocco (non essendo una dichiarazione non importa).

Sembra che C# 6 abbia cambiato questa regola, in particolare rendendo legittimo l'ultimo esempio (e il mio codice originale), anche se non capisco esattamente come.

Per favore correggimi se ho fatto degli errori in questi esempi.

+1

Questo codice viene compilato per me con Roslyn e con Mono 4.1.0.0 (costruito da capo alcune settimane fa). –

+0

Ricevo lo stesso errore (l'ultimo) in VS2013 (.NET 4.5). –

+0

@JonSkeet: whoa, quindi questo potrebbe essere un * bug * in csc? O_o (oddio, mi sento così bene adesso) – Mints97

risposta

9

La risposta, purtroppo ridotta, di Sriram Sakthivel è corretta. C# ha una regola, che ho scritto su un numero di volte, che richiede che ogni utilizzo dello stesso nome semplice in un blocco abbia lo stesso significato.

Accetto che il messaggio di errore sia estremamente confuso. Ho svolto un lungo lavoro a Roslyn per garantire che questo messaggio di errore fosse meno confuso in Roslyn, lavoro che potrebbe essere stato inutile.

È possibile leggere i miei articoli su questa regola, e il lavoro che ho fatto per migliorare il messaggio di errore, qui: (. Start in basso, questi sono in ordine cronologico inverso)

http://ericlippert.com/tag/simple-names/

Jon e altri notano correttamente che nelle versioni prerelease di C# 6, non si ottiene l'errore per il codice. Credo che dopo aver lasciato la squadra, sia stato fatto più lavoro su questa condizione di errore, e probabilmente è stato più rilassato essere più indulgente.

Il principio di "un nome deve significare solo una cosa" è buono, ma è difficile da implementare, difficile da spiegare e la fonte di molte domande su questo sito, quindi probabilmente il team di progettazione ha deciso per andare con una regola più facile da spiegare e implementare. Che cos'è esattamente questa regola, non lo so; Non ho ancora avuto il tempo di consultare il codice sorgente di Roslyn e vedere come quella parte del codice si è evoluta nel tempo.

AGGIORNAMENTO: Le mie spie nel team di Roslyn mi informano che è stato eseguito il commit 23891e, che abbrevierà notevolmente la ricerca. :-)

AGGIORNAMENTO: la regola è andata per il meglio; guarda la risposta di Jon per i dettagli.

+2

Tuttavia, "in tutto il blocco" significa includere blocchi nidificati?Se è così, è banale da violare dichiarando due variabili locali in diversi blocchi annidati. In caso contrario, è difficile vedere come viene violato dal codice che utilizza la variabile solo una volta per blocco non annidato. –

+2

@JonSkeet: concordo sul fatto che la formulazione non è chiara. L'intenzione è che il nome sia univoco in tutto lo "spazio" locale di dichiarazione "più alto" che * direttamente * contenga un uso di un nome semplice. Questo è volutamente pensato per consentire '{int x; } {int x; } 'e' for (int x ...) {} per (int x ...) {} 'e' Dove (x => y (x)). Seleziona (x => z (x)) ' –

+0

Destra. Sono contento che sia come sospettavo, almeno. Ora per cercare di capire come esprimerlo più chiaramente ... –

13

Eric Lippert ha bloggato su questo Simple names are not so simple.

I nomi semplici (senza un nome completo) possono sempre significare solo una cosa in un blocco di codice. Se lo violate ci sarà un CS0135 compiler error.

Nel metodo method, test è un nome semplice, che significa due cose. Non è permesso in C#.

Se si effettua il campo di test si utilizza un nome qualificato anziché un nome semplice, l'errore del compilatore scompare.

private void method(int aaa) 
{ 
    TestDelegate del = test => aaa++; 

    this.test++; 
} 

Oppure, se si effettua l'accesso campo di prova in un blocco diverso, compilatore sarà felice per la compilazione.

private void method(int aaa) 
{ 
    TestDelegate del = (int test) => aaa++; 

    { 
     test++; 
    } 
} 

Ora non dovete due significato diverso per lo stesso nome semplice test. perché il secondo test vive in un blocco diverso.

A partire da ora (aprile 2015) questa risposta è valida. A partire da C# 6.0 le cose sono cambiate. Questa regola è andata via. Per ulteriori dettagli, consultare Jon's answer.

+0

prova ad aggiungere un metodo chiamato 'void foo (int test) {}' quindi prova a commentare la riga con '++'. OP è al 100% giusto questo è un bug nel compilatore. anche per quanto ne so, questo non ha nulla a che vedere con il post sul blog di Eric. –

+1

Tranne che * è * permesso in C# e viene compilato su compilatori moderni ... –

+0

@AK_ Che succede con il metodo 'foo'? Non capisco. Perché dovresti rivendicare questo bug nel compilatore? Il post di Eric parla dello stesso argomento. I nomi semplici non possono significare cose diverse nello stesso ambito di dichiarazione. –

4

Ho sollevato Roslyn issue 2110 per questo - la specifica C# 6 sta cambiando per consentire questo. Mads Torgersen ha indicato che il cambiamento è di progettazione:

The rule was well intended, in that it was supposed to minimize the risk of "moving code around" in a way that would silently change its meaning. However, everyone we talked to only seemed to know about the rule from when it was causing them unnecessary grief - no-one had ever been saved by its prohibitions.

Furthermore, the extra tracking necessary in the compiler to enforce the rule interactively was causing significant complications to the code, as well as non-trivial performance overhead. Now, if it were a good rule we would have kept it anyway, but it isn't! So it's gone.

E 'semplice per dimostrare l'incoerenza tra le versioni del compilatore senza delegati coinvolti:

class Test 
{ 
    static int test; 

    static void Main() 
    { 
     { 
      int test = 10; 
     } 
     test++; 
    } 
} 

La relativa sezione del C# 5 spec è 7.6. 2.1, che dà questa regola:

7.6.2.1 Invariant meaning in blocks

For each occurrence of a given identifier as a full simple-name (without a type argument list) in an expression or declarator, within the local variable declaration space (§3.3) immediately enclosing that occurrence, every other occurrence of the same identifier as a full simple-name in an expression or declarator must refer to the same entity. This rule ensures that the meaning of a name is always the same within a given block, switch block, for-, foreach- or using-statement, or anonymous function.

Personalmente, penso che questo sia un bit meno che ideale di specifiche. Non è chiaro se "all'interno di un determinato blocco" intende includere blocchi annidati. Ad esempio, questo va bene:

static void Main() 
{ 
    { 
     int x = 10; 
    } 

    { 
     int x = 20; 
    } 
} 

... nonostante il fatto che x viene utilizzato per riferirsi a diverse variabili all'interno del blocco "di primo livello" del metodo Main, se si include la nidificazione. Quindi, se si considera quel blocco, si viola l'affermazione che "questa regola garantisce che il significato di un nome sia sempre lo stesso all'interno di un determinato blocco [...]" Tuttavia, credo che il blocco non sia controllato per questo, perché non è lo spazio di dichiarazione delle variabili "immediatamente incluso" di qualsiasi utilizzo di x.

Quindi, a mio avviso, l'errore è coerente con la prima parte citata della specifica, ma non è coerente con la frase finale, che è una nota nelle specifiche ECMA.

Prenderò nota della scarsa formulazione delle specifiche e cercherò di risolverlo per la prossima versione delle specifiche ECMA.

+0

Questo è sorprendente ... La dicitura * può * essere interpretata in modo che il codice come il mio esempio o il tuo primo esempio sia illegale ... ma suona così sbagliato, i blocchi nidificati hanno il loro (più o meno) scope separato! – Mints97

+0

@ Mints97: Il mio primo esempio è molto simile a un esempio nella specifica che viene fornito come esempio di qualcosa che * significa * essere illegale. Strano, lo so. –

Problemi correlati