Perché posso dichiarare una variabile figlio con lo stesso nome di una variabile nell'ambito genitore?


23

Di recente ho scritto del codice in cui ho riutilizzato involontariamente un nome di variabile come parametro di un'azione dichiarata all'interno di una funzione che ha già una variabile con lo stesso nome. Per esempio:

var x = 1;
Action<int> myAction = (x) => { Console.WriteLine(x); };

Quando ho notato la duplicazione, sono stato sorpreso di vedere che il codice è stato compilato ed eseguito perfettamente, il che non è un comportamento che mi aspetterei in base a ciò che so di scope in C #. Un rapido Googling alzato domande in modo che si lamentano che il codice simile non produrre un errore, ad esempio Lambda Ambito Chiarimento . (Ho incollato quel codice di esempio nel mio IDE per vedere se sarebbe stato eseguito, giusto per essere sicuro; funziona perfettamente.) Inoltre, quando entro nella finestra di dialogo Rinomina in Visual Studio, il primo xviene evidenziato come conflitto di nomi.

Perché questo codice funziona? Sto usando C # 8 con Visual Studio 2019.


1
Il lambda viene spostato in un metodo su una classe generata dal compilatore e quindi l'intero xparametro di quel metodo viene spostato dall'ambito. Vedi sharplab per un esempio.
Lasse V. Karlsen,

6
Vale probabilmente la pena notare che questo non verrà compilato quando si sceglie come target C # 7.3, quindi questo sembra essere esclusivo di C # 8.
Jonathon Chase

Anche il codice nella domanda collegata si compila bene in sharplab . Questo potrebbe essere un cambiamento recente.
Lasse V. Karlsen,

2
trovato una vittima (senza risposta): stackoverflow.com/questions/58639477/...
bolov

Risposte:


26

Perché questo codice funziona? Sto usando C # 8 con Visual Studio 2019.

Hai risposto alla tua domanda! È perché stai usando C # 8.

La regola da C # 1 a 7 era: un nome semplice non può essere usato per indicare due cose diverse nello stesso ambito locale. (La regola effettiva era leggermente più complessa di quella ma descriveva quanto fosse noiosa; vedi la specifica C # per i dettagli.)

L'intenzione di questa regola era di prevenire il tipo di situazione di cui stai parlando nel tuo esempio, in cui diventa molto facile essere confuso sul significato del locale. In particolare, questa regola è stata progettata per prevenire confusioni come:

class C 
{
  int x;
  void M()
  {
    x = 123;
    if (whatever)
    {
      int x = 356;
      ...

E ora abbiamo una situazione in cui all'interno del corpo M, xmezzi sia this.xe locale x.

Sebbene ben intenzionato, ci sono stati una serie di problemi con questa regola:

  • Non è stato implementato secondo le specifiche. Vi erano situazioni in cui un nome semplice poteva essere usato come, ad esempio, sia un tipo che una proprietà, ma questi non venivano sempre contrassegnati come errori perché la logica di rilevamento degli errori era errata. (Vedi sotto)
  • I messaggi di errore erano formulati in modo confuso e riportati in modo incoerente. Vi erano più messaggi di errore diversi per questa situazione. Hanno identificato in modo incoerente l'autore del reato; cioè, a volte l' uso interiore sarebbe stato richiamato, a volte l' esterno , e talvolta era solo confuso.

Ho fatto uno sforzo nella riscrittura di Roslyn per risolvere questo problema; Ho aggiunto alcuni nuovi messaggi di errore e reso coerenti quelli precedenti per quanto riguarda la segnalazione dell'errore. Tuttavia, questo sforzo era troppo piccolo, troppo tardi.

Il team di C # ha deciso per C # 8 che l'intera regola stava causando più confusione di quanto impedisse, e la regola è stata ritirata dalla lingua. (Grazie a Jonathon Chase per determinare quando è avvenuta la pensione.)

Se sei interessato a conoscere la storia di questo problema e come ho tentato di risolverlo, vedi questi articoli che ho scritto al riguardo:

https://ericlippert.com/2009/11/02/simple-names-are-not-so-simple/

https://ericlippert.com/2009/11/05/simple-names-are-not-so-simple-part-two/

https://ericlippert.com/2014/09/25/confusing-errors-for-a-confusing-feature-part-one/

https://ericlippert.com/2014/09/29/confusing-errors-for-a-confusing-feature-part-two/

https://ericlippert.com/2014/10/03/confusing-errors-for-a-confusing-feature-part-three/

Alla fine della terza parte ho notato che c'era anche un'interazione tra questa funzione e la funzione "Colore colore", ovvero la funzione che consente:

class C
{
  Color Color { get; set; }
  void M()
  {
    Color = Color.Red;
  }
}

Qui abbiamo usato il nome semplice Colorper fare riferimento sia this.Coloral tipo enumerato Color; secondo una lettura rigorosa della specifica questo dovrebbe essere un errore, ma in questo caso la specifica era errata e l'intenzione era di consentirla, poiché questo codice non è ambiguo e sarebbe irritante far cambiare lo sviluppatore.

Non ho mai scritto quell'articolo descrivendo tutte le strane interazioni tra queste due regole, e sarebbe un po 'inutile farlo ora!


Il codice nella domanda non riesce a compilare per C # 6, 7, 7.1, 7.2 e 7.3, fornendo "CS0136: un locale o un parametro denominato 'x' non può essere dichiarato in questo ambito perché quel nome ...". Sembra che la regola sia ancora in vigore fino a C # 8
Jonathon Chase il

@JonathonChase: grazie!
Eric Lippert,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.