In der for-Schleife deklarierte Variable ist eine lokale Variable?

133

Ich benutze C # schon ziemlich lange, habe aber Folgendes nie bemerkt:

 public static void Main()
 {
     for (int i = 0; i < 5; i++)
     {

     }

     int i = 4;  //cannot declare as 'i' is declared in child scope                
     int A = i;  //cannot assign as 'i' does not exist in this context
 }

Warum kann ich den Wert von 'i' nicht außerhalb des for-Blocks verwenden, wenn ich keine Variable mit diesem Namen deklarieren kann?

Ich dachte, dass die von einer for-Schleife verwendete Iteratorvariable nur in ihrem Umfang gültig ist.

John V.
quelle
8
Weil der äußere Blockbereich den Bereich der for-Schleife enthält
V4Vendetta
3
Ein Weg , ich denke , es (und einige Code - Styleguides dies erfordern, vor allem für dynamische typisierte Sprachen) ist , dass alle Variablen in einem Umfang erklärt konnte zu Beginn dieses Bereichs erklärt werden, was bedeutet , dass Ihre Funktion neu geschrieben werden konntenint i, A; for(int i = 0; i < 5; i++){ } i=4; A=i
Keith
2
@ V4Vendetta: Es ist eher umgekehrt. Der innere Block ist eine Blackbox für den übergeordneten Block.
Sebastian Mach
4
Abgesehen von den technischen Gründen, warum dies nicht möglich ist, warum sollte dies (oder eine Variante davon) jemals eine vernünftige Sache sein?! Es dient offensichtlich einem anderen Zweck, also geben Sie ihm einen anderen Namen.
Dave
Ich habe das überhaupt nicht bemerkt, aber es scheint eine völlig blöde Einschränkung zu sein.
Alan2here

Antworten:

119

Der Grund, warum Sie eine Variable mit demselben Namen sowohl in der for-Schleife als auch außerhalb der for-Schleife nicht definieren dürfen, liegt darin, dass Variablen im äußeren Bereich im inneren Bereich gültig sind. Dies bedeutet, dass es innerhalb der for-Schleife zwei 'i'-Variablen geben würde, wenn dies zulässig wäre.

Siehe: MSDN-Bereiche

Speziell:

Der Gültigkeitsbereich einer in einer Deklaration für lokale Variablen deklarierten lokalen Variablen (Abschnitt 8.5.1) ist der Block, in dem die Deklaration erfolgt.

und

Der Gültigkeitsbereich einer lokalen Variablen, die in einem for-Initialisierer einer for-Anweisung (Abschnitt 8.8.3) deklariert ist, ist der for-Initialisierer, die for-Bedingung, der for-Iterator und die enthaltene Anweisung der for-Anweisung.

Und außerdem: Deklarationen lokaler Variablen (Abschnitt 8.5.1 der C # -Spezifikation)

Speziell:

Der Gültigkeitsbereich einer lokalen Variablen, die in einer Deklaration für lokale Variablen deklariert ist, ist der Block, in dem die Deklaration erfolgt. Es ist ein Fehler, auf eine lokale Variable an einer Textposition zu verweisen, die vor dem Deklarator der lokalen Variablen der lokalen Variablen steht. Im Bereich einer lokalen Variablen ist es ein Fehler bei der Kompilierung, eine andere lokale Variable oder Konstante mit demselben Namen zu deklarieren.

(Hervorhebung von mir.)

Dies bedeutet, dass der Bereich iinnerhalb Ihrer for-Schleife die for-Schleife ist. Während der Umfang der iAußenseite Ihrer for-Schleife die gesamte Hauptmethode plus die for-Schleife ist. Das heißt, Sie hätten zwei Vorkommen iinnerhalb der Schleife, die gemäß den obigen Angaben ungültig sind.

Der Grund, warum Sie dies nicht dürfen, int A = i;ist, dass int ies nur für die Verwendung innerhalb der forSchleife vorgesehen ist. Somit ist es außerhalb der forSchleife nicht mehr zugänglich .

Wie Sie sehen können, sind beide Probleme auf das Scoping zurückzuführen. Das erste Problem ( int i = 4;) würde zu zwei iVariablen innerhalb des forSchleifenbereichs führen. Während int A = i;würde der Zugang zu einer Variablen führen , dass der Rahmen ist aus.

Sie können stattdessen deklarieren i, dass der Gültigkeitsbereich für die gesamte Methode gilt, und ihn dann sowohl in der Methode als auch im for-loop-Bereich verwenden. Dadurch wird vermieden, dass eine der beiden Regeln verletzt wird.

public static void Main()
{
    int i;

    for (i = 0; i < 5; i++)
    {

    }

    // 'i' is only declared in the method scope now, 
    // no longer in the child scope -> valid.
    i = 4;

    // 'i' is declared in the method's scope -> valid. 
    int A = i;
}

EDIT :

Der C # -Compiler könnte natürlich geändert werden, damit dieser Code vollständig gültig kompiliert werden kann. Immerhin ist dies gültig:

for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

for (int i = 5; i > 0; i--)
{
    Console.WriteLine(i);
}

Aber wäre es wirklich vorteilhaft für Ihre Lesbarkeit und Wartbarkeit von Code, Code schreiben zu können, wie zum Beispiel:

public static void Main()
{
    int i = 4;

    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
    }

    for (int i = 5; i > 0; i--)
    {
        Console.WriteLine(i);
    }

    Console.WriteLine(i);
}

Denken Sie hier über mögliche Fehler nach. Druckt der letzte iAusdruck 0 oder 4 aus? Dies ist ein sehr kleines Beispiel, das leicht zu verfolgen und zu verfolgen ist, aber definitiv viel weniger wartbar und lesbar ist, als das Äußere imit einem anderen Namen deklariert zu haben .

NB:

Bitte beachten Sie, dass sich die Gültigkeitsregeln von C # von den Gültigkeitsregeln von C ++ unterscheiden . In C ++ befinden sich Variablen nur im Bereich, von dem aus sie bis zum Ende des Blocks deklariert werden. Was Ihren Code zu einem gültigen Konstrukt in C ++ machen würde.

Johannes Kommer
quelle
Sinnvoll, ich denke, es sollte ein Fehler in der inneren iVariablen sein; das scheint mir offensichtlicher.
George Duckett
Aber der Bereich ist für Befehl und seine {}? Oder bedeutet es sein Elternteil {}?
John V
2
Nun, wenn ich 'A' NACH der for-Anweisung deklariere, ist es in der for-Schleife nicht gültig, da es später deklariert wird. Deshalb verstehe ich nicht, warum der gleiche Name nicht verwendet werden kann.
John V
9
Vielleicht sollte diese Antwort der Vollständigkeit halber darauf hinweisen, dass sich die C # -Bereichsregeln in diesem Fall von denen für C ++ unterscheiden . In C ++ befinden sich Variablen nur im Gültigkeitsbereich, von dem aus sie bis zum Ende des Blocks deklariert werden (siehe msdn.microsoft.com/en-us/library/b7kfh662(v=vs.80).aspx ).
AAT
2
Beachten Sie, dass Java einen Zwischenansatz zwischen C ++ und C # verfolgt: Da dies das Beispiel des OP ist, ist es in Java gültig. Wenn jedoch die äußere iDefinition vor die for-Schleife verschoben wird, wird die innere iDefinition als ungültig markiert.
Nicola Musatti
29

J.Kommer Antwort ist richtig: kurz, es illegal ist für eine lokale Variable in einen deklariert wird lokalen Variable Deklarationsraumes , die überlappt eine anderen lokalen Variable Deklarationsraum , der ein lokal mit dem gleichen Namen hat.

Es gibt eine zusätzliche Regel von C #, die auch hier verletzt wird. Die zusätzliche Regel lautet, dass es unzulässig ist, einen einfachen Namen zu verwenden, um auf zwei verschiedene Entitäten in zwei verschiedenen überlappenden Deklarationsräumen für lokale Variablen zu verweisen. Ihr Beispiel ist also nicht nur illegal, sondern auch illegal:

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

Weil jetzt der einfache Name "x" im Deklarationsraum der lokalen Variablen von "y" verwendet wurde, um zwei verschiedene Dinge zu bedeuten - "this.x" und das lokale "x".

Weitere Analysen dieser Probleme finden Sie unter http://blogs.msdn.com/b/ericlippert/archive/tags/simple+names/ .

Eric Lippert
quelle
2
Es ist auch interessant festzustellen, dass Ihr Beispielcode kompiliert wird, wenn Sie die Änderung vornehmenint y = this.x;
Phil
4
@Phil: Richtig. this.xist kein einfacher Name .
Eric Lippert
13

Es gibt eine Möglichkeit, idie Methode nach der Schleife zu deklarieren und zu verwenden :

static void Main()
{
    for (int i = 0; i < 5; i++)
    {

    }

    {
        int i = 4;
        int A = i;
    }
}

Sie können dies in Java tun (es könnte von C stammen, da bin ich mir nicht sicher). Es ist natürlich ein bisschen chaotisch für einen Variablennamen.

Chris S.
quelle
Ja, das stammt aus C / C ++.
Branko Dimitrijevic
1
Das wusste ich vorher nicht. Ordentliche Sprachfunktion!
@ MathiasLykkegaardLorenzen ja
Chris S
7

Wenn Sie i vor Ihrer forSchleife deklariert haben , sollte es Ihrer Meinung nach noch gültig sein, sie innerhalb der Schleife zu deklarieren?

Nein, denn dann würde sich der Umfang der beiden überschneiden.

Was nicht möglich int A=i;ist, liegt einfach daran, dass es inur in der forSchleife existiert, wie es sein sollte.

Widor
quelle
7

Zusätzlich zu J.Kommers Antwort (+1 übrigens). Es gibt dies im Standard für den NET-Bereich:

block Wenn Sie eine Variable innerhalb eines Blockkonstrukts wie einer If-Anweisung deklarieren, gilt der Gültigkeitsbereich dieser Variablen nur bis zum Ende des Blocks. Die Lebensdauer beträgt bis zum Ende des Verfahrens.

Prozedur Wenn Sie eine Variable innerhalb einer Prozedur deklarieren, jedoch außerhalb einer If-Anweisung, liegt der Gültigkeitsbereich bis zur End Sub- oder End-Funktion. Die Lebensdauer der Variablen beträgt bis zum Ende der Prozeduren.

Daher ist das im for-Schleifen-Header dekalierte int i nur während des for-Schleifenblocks im Gültigkeitsbereich, ABER seine Lebensdauer dauert bis zur Main()Vervollständigung des Codes.

ChrisBD
quelle
5

Der einfachste Weg, darüber nachzudenken, besteht darin, die äußere Deklaration von I über die Schleife zu verschieben. Es sollte dann offensichtlich werden.

Es ist in beiden Fällen der gleiche Bereich und kann daher nicht durchgeführt werden.

Andrew Barber
quelle
4

Auch die Regeln von C # sind für die strikte Programmierung oft nicht erforderlich, dienen jedoch dazu, Ihren Code sauber und lesbar zu halten.

Zum Beispiel hätten sie es so machen können, dass wenn Sie es nach der Schleife definieren, es in Ordnung ist, aber jemand, der Ihren Code liest und die Definitionszeile verpasst hat, könnte denken, dass es mit der Variablen der Schleife zu tun hat.

Kerl
quelle
2

Kommers Antwort ist technisch korrekt. Lassen Sie es mich mit einer lebendigen Blind-Screen-Metapher umschreiben.

Zwischen dem for-Block und dem umschließenden äußeren Block befindet sich ein Einweg-Blindbildschirm, sodass der Code innerhalb des for-Blocks den äußeren Code sehen kann, der Code im äußeren Block jedoch den Code im Inneren nicht sehen kann.

Da der äußere Code nicht nach innen sehen kann, kann er nichts verwenden, was im Inneren deklariert ist. Da der Code im for-Block sowohl innen als auch außen sichtbar ist, kann eine an beiden Stellen deklarierte Variable nicht eindeutig namentlich verwendet werden.

Entweder du siehst es nicht oder du C #!

Forscher
quelle
0

Betrachten Sie es so, als könnten Sie ein intin einem usingBlock deklarieren :

using (int i = 0) {
  // i is in scope here
}
// here, i is out of scope

Da dies intjedoch nicht implementiert ist IDisposable, kann dies nicht durchgeführt werden. Es kann jedoch jemandem helfen, zu visualisieren, wie eine intVariable in einem privaten Bereich platziert wird.

Ein anderer Weg wäre zu sagen:

if (true) {
  int i = 0;
  // i is in scope here
}
// here, i is out of scope

Hoffe, dies hilft zu visualisieren, was los ist.

Ich mag diese Funktion wirklich, da das Deklarieren von intaus der forSchleife den Code schön und eng hält.

jp2code
quelle
WTF? Wenn Sie ein NEG markieren möchten, lassen Sie die Bälle sagen, warum.
JP2-Code
Nicht der Downvoter und ich denke, der Vergleich mit usingist ganz ok, obwohl die Semantik ziemlich unterschiedlich ist (was vielleicht der Grund ist, warum er downvotiert wurde). Beachten Sie, dass dies if (true)redundant ist. Entfernen Sie es und Sie haben einen Scoping-Block.
Abel