C# variabele scoping: ‘x’ kan niet in dit bereik worden gedeclareerd omdat dit een andere betekenis zou geven aan ‘x’

Dit resulteert in:

Fout 1 Een lokale variabele met de naam ‘var’
kan niet worden gedeclareerd in dit bereik
omdat het een ander zou geven
wat betekent ‘var’, wat al is
gebruikt in een ‘kind’-bereik om aan te duiden
iets anders.

Niets wereldschokkends, maar is dit niet gewoon verkeerd? Een collega-ontwikkelaar en ik vroegen ons af of de eerste aangifte in een ander bereik zou moeten vallen, dus de tweede aangifte kan de eerste aangifte niet verstoren.

Waarom kan C# geen onderscheid maken tussen de twee bereiken? Moet de eerste IF-scope niet volledig gescheiden zijn van de rest van de methode?

Ik kan var niet aanroepen van buiten de if, dus de foutmelding is onjuist, omdat de eerste var niet relevant is in de tweede scope.


Antwoord 1, autoriteit 100%

Het probleem hier is grotendeels een kwestie van goede praktijken en het voorkomen van onbedoelde fouten. Toegegeven, de C#-compiler zou theoretischzo kunnen worden ontworpen dat er hier geen conflict is tussen de scopes. Dit zou echter veel moeite zijn voor weinig winst, zoals ik het zie.

Bedenk dat als de declaratie van varin het bovenliggende bereik vóórde if-instructie zou zijn, er een onoplosbaar naamgevingsconflict zou zijn. De compiler maakt eenvoudig geen onderscheid tussen de volgende twee gevallen. Analyse wordt gedaan puur gebaseerd op reikwijdte, en niet op volgorde van aangifte/gebruik, zoals u lijkt te verwachten.

De theoretisch acceptabele (maar nog steeds ongeldig wat C# betreft):

if(true)
{
    string var = "VAR";
}
string var = "New VAR!";

en het onaanvaardbare (omdat het de bovenliggende variabele zou verbergen):

string var = "New VAR!";
if(true)
{
    string var = "VAR";
}

worden beide precies hetzelfde behandeld in termen van variabelen en scopes.

Is er in dit secenario een echte reden waarom je niet gewoon een van de variabelen een andere naam kunt geven? Ik neem aan (hoop) dat je werkelijke variabelen niet varheten, dus ik zie dit niet echt als een probleem. Als je nog steeds van plan bent om dezelfde variabelenaam opnieuw te gebruiken, plaats ze dan gewoon in hetzelfde bereik:

if(true)
{
    string var = "VAR";
}
{
    string var = "New VAR!";
}

Dit is echter geldig voor de compiler, maar kan tot enige verwarring leiden bij het lezen van de code, dus ik raad het in bijna elk geval af.


Antwoord 2, autoriteit 76%

is dit niet gewoon verkeerd?

Nee, dit is helemaal niet verkeerd. Dit is een correcte implementatie van paragraaf 7.5.2.1 van de C#-specificatie, “Eenvoudige namen, invariante betekenissen in blokken”.

In de specificatie staat:


Voor elk voorkomen van een gegeven
identifier als een eenvoudige naam in an
uitdrukking of declarator, binnen de
lokale variabele declaratieruimte
van dat voorval, elke
ander voorkomen van hetzelfde
identifier als een eenvoudige naam in an
uitdrukking of declarator moet naar hetzelfde verwijzen
entiteit. Deze regel zorgt ervoor dat de
betekenis van een naam is altijd hetzelfde
binnen een bepaald blok, schakelblok,
for-, foreach- of using-statement, of
anonieme functie.


Waarom kan C# geen onderscheid maken tussen de twee bereiken?

De vraag is onzinnig; het is duidelijk dat de compiler isin staat om onderscheid te maken tussen de twee scopes. Als de compiler geen onderscheid kon maken tussen de twee scopes, hoe kon de fout dan worden geproduceerd? De foutmelding zegtdat er twee verschillende scopes zijn, en daarom zijn de scopes gedifferentieerd!

Moet de eerste IF-scope niet volledig gescheiden zijn van de rest van de methode?

Nee, dat mag niet. Het bereik (en de declaratieruimte van de lokale variabele) gedefinieerd door de blokinstructie in de consequentie van de voorwaardelijke instructie is lexicaal een deel van het buitenste blok dat de hoofdtekst van de methode definieert. Daarom zijn regels over de inhoud van het buitenste blok van toepassing op de inhoud van het binnenste blok.

Ik kan var niet aanroepen van buiten de if,
dus de foutmelding is verkeerd, omdat
de eerste var heeft geen relevantie in de
tweede bereik.

Dit is helemaal verkeerd. Het is misleidend om te concluderen dat alleen omdat de lokale variabele niet langer in het bereik is, het buitenste blok geen fout bevat. De foutmelding is correct.

De fout hier heeft niets te maken met de vraag of het bereik van een variabele het bereik van een andere variabele overlapt; het enige dat hier relevant is, is dat je een blok hebt — het buitenste blok — waarin dezelfde eenvoudige naam wordt gebruikt om naar twee totaal verschillende dingen te verwijzen. C# vereist dat een eenvoudige naam één betekenis heeft in het blok dat deze voor het eerst gebruikt.

Bijvoorbeeld:

class C 
{
    int x;
    void M()
    { 
        int x = 123;
    }
}

Dat is volkomen legaal; de omvang van de buitenste x overlapt de omvang van de binnenste x, maar dat is geen fout. Wat is een fout is:

class C 
{
    int x;
    void M()
    { 
        Console.WriteLine(x);
        if (whatever)
        {
            int x = 123;
        }
    }
}

omdat de eenvoudige naam “x” nu twee verschillende dingen betekent in de body van M — het betekent “this.x” en de lokale variabele “x”. Het is verwarrend voor ontwikkelaars en codebeheerders wanneer dezelfde eenvoudige naam twee totaal verschillende dingen in hetzelfde blokbetekent, dus dat is illegaal.

We staan ​​toe dat parallelle blokken dezelfde eenvoudige naam bevatten die op twee verschillende manieren wordt gebruikt; dit is legaal:

class C 
{
    int x;
    void M()
    { 
        if (whatever)
        {
            Console.WriteLine(x);
        }
        if (somethingelse)
        {
            int x = 123;
        }
    }
}

omdat nu het enige blok dat twee inconsistente vormen van gebruik van x bevat, het buitenste blok is, en dat blok directgeen gebruik van “x” bevat, alleen indirect.


Antwoord 3, autoriteit 24%

Dit is geldig in C++, maar een bron voor veel bugs en slapeloze nachten. Ik denk dat de C#-mannen hebben besloten dat het beter is om een ​​waarschuwing/foutmelding te geven, aangezien het in de overgrote meerderheid van de gevallen een bug is in plaats van iets dat de codeur eigenlijk wil.

Hieris een interessante discussie over uit welke delen van de specificatie deze fout komt.

BEWERKEN (enkele voorbeelden) —–

In C++ is het volgende geldig (en het maakt niet echt uit of de buitenste declaratie voor of na de binnenste scope is, het is alleen interessanter en vatbaarder voor bugs als het ervoor is).

void foo(int a)
{
    int count = 0;
    for(int i = 0; i < a; ++i)
    {
        int count *= i;
    }
    return count;
}

Stel je nu voor dat de functie een paar regels langer is en dat het gemakkelijk is om de fout niet te zien. De compiler klaagt nooit (niet vroeger, niet zeker over nieuwere versies van C++), en de functie retourneert altijd 0.

Het gedrag is duidelijk een bug, dus het zou goed zijn als een c++-lint-programma of de compiler dit aangeeft. Als het geen bug is, kun je er gemakkelijk omheen werken door gewoon de interne variabele te hernoemen.

Om nog erger te maken, herinner ik me dat GCC en VS6 verschillende meningen hadden over waar de tellervariabele in for-loops thuishoorde. De een zei dat het tot de buitenste scope behoorde en de ander zei van niet. Een beetje vervelend om aan platformonafhankelijke code te werken. Laat me je nog een ander voorbeeld geven om mijn aantal lijnen op peil te houden.

for(int i = 0; i < 1000; ++i)
{
    if(array[i] > 100)
        break;
}
printf("The first very large value in the array exists at %d\n", i);

Deze code werkte in VS6 IIRC en niet in GCC. Hoe dan ook, C# heeft een paar dingen opgeschoond, wat goed is.

Other episodes