Zmienna zadeklarowana w pętli for jest zmienną lokalną?

133

Używam języka C # od dłuższego czasu, ale nigdy nie zdawałem sobie sprawy z następujących kwestii:

 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
 }

Dlaczego więc nie mogę używać wartości „i” poza blokiem for, jeśli nie pozwala mi to zadeklarować zmiennej o tej nazwie?

Pomyślałem, że zmienna iteratora używana przez pętlę for jest ważna tylko w swoim zakresie.

John V
źródło
8
Ponieważ zewnętrzny zakres blokowy obejmuje zakres pętli for
V4Vendetta
3
Myślę o tym (i niektóre przewodniki po stylach kodu wymagają tego, szczególnie w przypadku języków z typami dynamicznymi), że wszystkie zmienne zadeklarowane w zakresie można zadeklarować na początku tego zakresu, co oznacza, że ​​funkcja może zostać ponownie napisanaint i, A; for(int i = 0; i < 5; i++){ } i=4; A=i
Keith,
2
@ V4Vendetta: Jest bardziej na odwrót. Blok wewnętrzny jest czarną ramką bloku nadrzędnego.
Sebastian Mach,
4
Pomijając techniczne powody, dla których nie jest to możliwe, dlaczego to (lub wariant tego) miałoby być kiedykolwiek rozsądne ?! Oczywiście służy to innemu celowi, więc nadaj mu inną nazwę.
Dave
W ogóle nie zdawałem sobie z tego sprawy, ale wydaje się, że jest to całkowicie głupie ograniczenie.
alan2here

Odpowiedzi:

119

Powodem, dla którego nie możesz definiować zmiennej o tej samej nazwie zarówno w pętli for, jak i poza nią, jest to, że zmienne w zakresie zewnętrznym są prawidłowe w zakresie wewnętrznym. Oznacza to, że gdyby było to dozwolone, w pętli for byłyby dwie zmienne „i”.

Zobacz: Zakresy MSDN

Konkretnie:

Zakres zmiennej lokalnej zadeklarowanej w deklaracji zmiennej lokalnej (sekcja 8.5.1) to blok, w którym występuje deklaracja.

i

Zakres zmiennej lokalnej zadeklarowanej w inicjatorze for instrukcji for (Rozdział 8.8.3) to inicjalizator for, warunek for, iterator for oraz instrukcja zawarta w instrukcji for.

A także: Deklaracje zmiennych lokalnych (sekcja 8.5.1 specyfikacji C #)

Konkretnie:

Zakres zmiennej lokalnej zadeklarowanej w deklaracji zmiennej lokalnej to blok, w którym deklaracja występuje. Błędem jest odwoływanie się do zmiennej lokalnej w pozycji tekstowej, która poprzedza deklarator zmiennej lokalnej zmiennej lokalnej. W zakresie zmiennej lokalnej błędem w czasie kompilacji jest zadeklarowanie innej zmiennej lokalnej lub stałej o tej samej nazwie.

(Podkreśl moje.)

Co oznacza, że ​​zakresem iwewnętrznej pętli for jest pętla for. Z kolei zasięg izewnętrznej pętli for to cała główna metoda plus pętla for. Oznacza to, że miałbyś dwa wystąpienia iwewnątrz pętli, które są nieprawidłowe zgodnie z powyższym.

Powodem, dla którego nie możesz tego zrobić, int A = i;jest to, że int ijest on przeznaczony tylko do użytku w forpętli. Dlatego nie jest już dostępny poza forpętlą.

Jak widać, obie te kwestie są wynikiem określania zakresu; pierwsza kwestia ( int i = 4;) spowodowałaby powstanie dwóch izmiennych w zakresie forpętli. Natomiast int A = i;skutkowałoby dostępem do zmiennej, która jest poza zakresem.

Zamiast tego można zadeklarować izakres dla całej metody, a następnie użyć go zarówno w metodzie, jak i w zakresie pętli for. Pozwoli to uniknąć złamania którejkolwiek zasady.

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;
}

EDYCJA :

Kompilator C # można oczywiście zmienić, aby umożliwić poprawną kompilację tego kodu. W końcu to jest ważne:

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

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

Ale czy naprawdę korzystne byłoby dla czytelności i łatwości utrzymania kodu, gdybyśmy mogli pisać taki kod, jak:

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);
}

Pomyśl o potencjale błędów tutaj, czy ostatni iwydruk wyświetla 0 lub 4? To bardzo mały przykład, który jest dość łatwy do naśladowania i śledzenia, ale jest zdecydowanie mniej łatwy w utrzymaniu i czytelności niż zadeklarowanie zewnętrznej ipod inną nazwą.

Uwaga:

Należy pamiętać, że reguły zakresu C # różnią się od reguł określania zakresu C ++ . W C ++ zmienne znajdują się tylko w zakresie od miejsca ich zadeklarowania do końca bloku. Co uczyniłoby twój kod prawidłową konstrukcją w C ++.

Johannes Kommer
źródło
To ma sens, ale myślę, że powinno to powodować błąd w izmiennej wewnętrznej ; wydaje mi się to bardziej oczywiste.
George Duckett
Ale zakres dotyczy polecenia i jego {}? Czy też oznacza jego rodzica {}?
John V
2
Cóż, jeśli zadeklaruję „A” PO instrukcji for, nie będzie to poprawne w pętli for, tak jak zostało to zadeklarowane później. Dlatego nie rozumiem, dlaczego nie można użyć tej samej nazwy.
John V
9
Być może ta odpowiedź powinna, dla kompletności, zwrócić uwagę, że reguły zakresu C # są inne niż te dla C ++ w tym przypadku. W C ++ zmienne znajdują się tylko w zakresie od miejsca ich zadeklarowania do końca bloku (patrz msdn.microsoft.com/en-us/library/b7kfh662(v=vs.80).aspx ).
AAT
2
Zwróć uwagę, że Java przyjmuje podejście pośrednie między C ++ i C #: ponieważ jest to przykład OP byłby prawidłowy w Javie, ale gdyby zewnętrzna idefinicja została przeniesiona przed pętlą for, wewnętrzna idefinicja zostanie oznaczona jako nieprawidłowa.
Nicola Musatti
29

Odpowiedź J.Kommera jest poprawna: w skrócie, niedozwolone jest deklarowanie zmiennej lokalnej w lokalnej przestrzeni deklaracji zmiennej, która nakłada się na inną lokalną przestrzeń deklaracji zmiennej, która ma zmienną lokalną o tej samej nazwie.

Istnieje również dodatkowa reguła języka C #, która jest tutaj naruszona. Dodatkową zasadą jest to, że użycie prostej nazwy w odniesieniu do dwóch różnych jednostek w dwóch różnych nakładających się przestrzeniach deklaracji lokalnych zmiennych jest niedozwolone. Więc twój przykład jest nie tylko nielegalny, ale także nielegalny:

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

Ponieważ teraz prosta nazwa „x” została użyta wewnątrz lokalnej przestrzeni deklaracji zmiennej „y”, aby oznaczać dwie różne rzeczy - „this.x” i lokalny „x”.

Więcej analiz tych problemów można znaleźć pod adresem http://blogs.msdn.com/b/ericlippert/archive/tags/simple+names/ .

Eric Lippert
źródło
2
Warto również zauważyć, że przykładowy kod skompiluje się, gdy wprowadzisz zmianęint y = this.x;
Phil
4
@Phil: Dobrze. this.xnie jest prostą nazwą .
Eric Lippert
13

Istnieje sposób zadeklarowania i użycia iwewnątrz metody po pętli:

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

    }

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

Możesz to zrobić w Javie (może pochodzić z C, nie jestem pewien). Jest to oczywiście trochę bałaganiarskie ze względu na nazwę zmiennej.

Chris S.
źródło
Tak, to pochodzi z C / C ++.
Branko Dimitrijevic
1
Nie wiedziałem tego wcześniej. Zgrabna funkcja językowa!
@MathiasLykkegaardLorenzen tak
Chris S
7

Jeśli zadeklarowałeś i przed swoimfor pętlą, czy uważasz, że powinno być nadal ważne, aby zadeklarować ją wewnątrz pętli?

Nie, ponieważ wtedy zakres tych dwóch pokrywałby się.

Jeśli chodzi o niemożność zrobienia int A=i;, to po prostu dlatego, że iistnieje tylko w forpętli, tak jak powinno.

Widor
źródło
7

Oprócz odpowiedzi J.Kommera (+1 btw). Jest to w standardzie dla zakresu NET:

blok Jeśli zadeklarujesz zmienną w konstrukcji blokowej, takiej jak instrukcja If, zakres tej zmiennej będzie obowiązywał tylko do końca bloku. Czas życia trwa do zakończenia procedury.

Procedura Jeśli deklarujesz zmienną w procedurze, ale poza jakąkolwiek instrukcją If, zakres obejmuje funkcję End Sub lub End. Czas życia zmiennej trwa do zakończenia procedur.

W związku z tym wartość int i zdekalowana w nagłówku pętli for będzie w zakresie tylko podczas bloku pętli for, ALE jej okres istnienia trwa do zakończenia Main()kodu.

ChrisBD
źródło
5

Najłatwiej o tym pomyśleć, przenosząc zewnętrzną deklarację I powyżej pętli. Wtedy powinno to stać się oczywiste.

Tak czy inaczej jest to ten sam zakres, dlatego nie można tego zrobić.

Andrew Barber
źródło
4

Również reguły języka C # są często niepotrzebne, jeśli chodzi o ścisłe programowanie, ale są po to, aby Twój kod był czysty i czytelny.

na przykład mogli to zrobić tak, że jeśli zdefiniujesz to po pętli, to jest w porządku, jednak ktoś, kto czyta twój kod i przegapił linię definicji, może pomyśleć, że ma to związek ze zmienną pętli.

Chłopak
źródło
2

Odpowiedź Kommera jest technicznie poprawna. Pozwólcie, że sparafrazuję to żywą metaforą ślepego ekranu.

Między blokiem for a otaczającym blokiem zewnętrznym znajduje się ekran ślepy w jedną stronę, tak że kod z wewnątrz bloku for może zobaczyć kod zewnętrzny, ale kod w bloku zewnętrznym nie może zobaczyć kodu wewnątrz.

Ponieważ kod zewnętrzny nie widzi wewnątrz, nie może używać niczego zadeklarowanego wewnątrz. Ale ponieważ kod w bloku for może widzieć zarówno wewnątrz, jak i na zewnątrz, zmienna zadeklarowana w obu miejscach nie może być jednoznacznie używana przez nazwę.

Więc albo tego nie widzisz, albo C #!

poszukiwacz
źródło
0

Spójrz na to w taki sam sposób, jakbyś mógł zadeklarować an intw usingbloku:

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

Jednak ponieważ intnie wdraża IDisposable, nie można tego zrobić. Może pomóc komuś wyobrazić sobie, jak plikint zmienna jest umieszczana w zakresie prywatnym.

Innym sposobem byłoby stwierdzenie,

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

Mam nadzieję, że pomoże to w wizualizacji tego, co się dzieje.

Bardzo podoba mi się ta funkcja, ponieważ zadeklarowanie intod wewnątrz forpętli sprawia, że ​​kod jest ładny i napięty.

jp2code
źródło
WTF? Jeśli zamierzasz oznaczyć NEG, miej jaja, aby powiedzieć dlaczego.
jp2code,
Nie przeciwnik i myślę, że porównanie usingjest całkiem w porządku, chociaż semantyka jest raczej inna (być może dlatego został odrzucony). Zauważ, że if (true)jest to zbędne. Usuń go i masz blok zakresu.
Abel