Dlaczego C # zezwala na {} bloki kodu bez poprzedzającej instrukcji?

86

Dlaczego C # pozwalają bloki kodu bez uprzedniego oświadczenia (np if, else, for, while)?

void Main()
{
    {   // any sense in this?
        Console.Write("foo");
    }
}
Seldon
źródło
70
Czy jest jakiś powód, dla którego nie powinno ?
Jamiec
30
Ale jak pokazują odpowiedzi, ma to większe znaczenie niż tylko „nie boli, więc pozwól na to”. O to chodzi w zadawaniu takich pytań.
Seldon
8
Pytanie +1 brzmi niewinnie, ale odpowiedzi naprawdę nauczyły mnie czegoś cennego
Nicolas78
7
@Akash: Bez pytania dlaczego nigdy nie będziemy lepsi.
Richard
7
@Akash: Wydaje się, że to elitarna postawa. Ludzie zawsze mają pytania i jeśli nie ma powodu, aby pytać dlaczego, to nie ma sensu mieć TAK. Ta strona nie jest przeznaczona do rozwiązywania naszych bezpośrednich problemów (chociaż tak się dzieje), ale do zapewnienia repozytorium pytań i odpowiedzi, które pomogą nam wszystkim być lepszymi programistami. TAK jest pytanie DLACZEGO! :-)
richard

Odpowiedzi:

90

W podanym przez ciebie kontekście nie ma to znaczenia. Zapisanie stałego ciągu do konsoli będzie działać w ten sam sposób w każdym miejscu przepływu programu. 1

Zamiast tego zazwyczaj używa się ich do ograniczenia zakresu niektórych zmiennych lokalnych. Jest to bardziej szczegółowo omówione tutaj i tutaj . Spójrz na odpowiedź João Angelo i odpowiedzi Chris Wallisa dla krótkich przykładów. Uważam, że to samo odnosi się również do niektórych innych języków ze składnią w stylu C, ale nie żeby były one odpowiednie dla tego pytania.


1 Chyba że zdecydujesz się być zabawny i stworzyć własną Consoleklasę za pomocą Write()metody, która robi coś zupełnie nieoczekiwanego.

BoltClock
źródło
24
Re: inne języki: zwykle, ale nie zawsze. JavaScript jest godnym uwagi wyjątkiem; zadeklarowanie zmiennej lokalnej w określonym bloku nie obejmuje zakresu zmiennej do bloku.
Eric Lippert
@EricLippert: Czy w C # deklarowanie zmiennej w bloku powoduje, że zmienna jest objęta zakresem z perspektywy czasu wykonywania? Z tego, co wiem, okresy istnienia zmiennych często nie są ograniczone blokami określającymi zakres, co można zaobserwować w projektach z różnymi językami, jeśli pierwszą rzeczą wykonywaną ze zmienną w pętli jest przekazanie jej jako outparametru do napisanej implementacji interfejsu w innym języku i ta implementacja czyta zmienną przed jej zapisaniem.
supercat
A w C ++, ze względu na RAII zamiast GC, jest znacznie bardziej przydatny.
Deduplicator
W C ++ działa podobnie do bloku try / catch, więc zmienne i klasy zadeklarowane w tym zakresie wyskakują ze stosu, gdy opuścisz ten zakres. Pomaga to zapobiegać kolizjom nazw i zmniejsza zużycie pamięci przez funkcję. Usuwa również te zmienne z automatycznego uzupełniania edytora kodu. Często uważam, że bloki try / catch są bardziej przydatne.
Kit10
144

Plik { ... }Ma co najmniej efekt uboczny wprowadzenie nowego zakresu zmiennych lokalnych.

Zwykle używam ich w switchinstrukcjach, aby zapewnić inny zakres dla każdego przypadku i w ten sposób pozwalając mi zdefiniować zmienną lokalną o tej samej nazwie w najbliższym możliwym miejscu ich użycia, a także zaznaczyć, że są one ważne tylko na poziomie przypadku.

João Angelo
źródło
6
Jeśli potrzebujesz lunet w swoich etui, są one za duże! Wyodrębnij metodę.
Jay Bazuzi
20
@Jay Bazuzi, tylko dlatego, że chcę mieć zmienną lokalną o tej samej nazwie w więcej niż jednym przypadku, nie oznacza, że ​​muszą być ogromne. Jesteś po prostu skacze do ogromnych wnioski ... :)
João Angelo
Gratulacje za odznakę „Świetna odpowiedź”! :)
BoltClock
1
@BoltClock, dzięki, potrzebuję tylko kilku pytań, {}jak zachować te złote odznaki ... :)
João Angelo
56

Jest to nie tyle funkcja C #, ile logiczny efekt uboczny wielu języków składni C, które używają nawiasów klamrowych do definiowania zakresu .

W naszym przykładzie nawiasy klamrowe nie mają żadnego efektu, ale w poniższym kodzie definiują zakres, a tym samym widoczność zmiennej:

Jest to dozwolone, ponieważ i wykracza poza zakres w pierwszym bloku i jest ponownie definiowane w następnym:

{
    {
        int i = 0;
    }

    {
        int i = 0;
    }
}

Jest to niedozwolone, ponieważ i wypadło poza zakres i nie jest już widoczne w zakresie zewnętrznym:

{
    {
        int i = 0;
    }

    i = 1;
}

I tak dalej i tak dalej.

Chris Wallis
źródło
1
Czytałem o rozbieżnościach w nomenklaturze nawiasów w różnych smakach angielskiego, ale w których smakach są {}znane jako nawiasy?
BoltClock
Dobre pytanie! Myślę, że mój mentor zasadził to w moim mózgu dziesięć lat temu. Prawdopodobnie powinienem nazywać je „nawiasami klamrowymi” lub „nawiasami klamrowymi”. Każdy do swojego ... en.wikipedia.org/wiki/…
Chris Wallis
10
W moim słowie '(' = nawias, '{' = nawias klamrowy, '[' = nawiasy kwadratowe
pb.
Poczekaj, nazwałbym je nawiasem klamrowym, a Wikipedia zawiera to nazewnictwo. Oxford English Dictionary definiuje takie użycie nawiasu jako: One of two marks of the form [ ] or ( ), and in mathematical use also {}, used for enclosing a word or number of words, a portion of a mathematical formula, or the like, so as to separate it from the context; W każdym razie nie są to nawiasy, ale „nawias klamrowy” wydaje się OK.
dumbledad
IME, „nawiasy klamrowe” i „nawiasy kwadratowe”.
cp.engr
18

Uważam za {}stwierdzenie, które może zawierać kilka stwierdzeń.

Rozważmy instrukcję if, która istnieje z wyrażenia logicznego, po którym następuje jedna instrukcja. To zadziała:

if (true) Console.Write("FooBar");

To również zadziała:

if (true)
{
  Console.Write("Foo");
  Console.Write("Bar");
}

Jeśli się nie mylę, nazywa się to instrukcją blokową.

Ponieważ {}może zawierać inne stwierdzenia, może również zawierać inne {}. Zakres zmiennej jest określony przez jej rodzica {}(instrukcję blokową).

Chodzi mi o to, że {}jest to tylko stwierdzenie, więc nie wymaga czy lub czegokolwiek ...

RBaarda
źródło
+1 To jest dokładnie to, czym są nawiasy klamrowe w językach typu C - tak zwana „instrukcja złożona”.
Michael Ekstrand
12

Ogólna zasada w językach C-składni brzmi: „wszystko pomiędzy { }powinno być traktowane jako pojedyncza instrukcja i może dotyczyć wszędzie tam, gdzie mogłoby to zrobić”:

  • Po if.
  • Po a for, whilelub do.
  • Gdziekolwiek w kodzie .

Pod każdym względem gramatyka języka zawierała to:

     <statement> :== <definition of valid statement> | "{" <statement-list> "}"
<statement-list> :== <statement> | <statement-list> <statement>

Oznacza to, że „instrukcja może składać się z (różnych elementów) lub nawiasu otwierającego, po którym następuje lista instrukcji (która może zawierać jedno lub więcej instrukcji), po której następuje nawias zamykający”. IE "a{ } blok może zastąpić dowolną instrukcję w dowolnym miejscu”. W tym w środku kodu.

Niedopuszczenie do umieszczenia { }bloku w dowolnym miejscu, w którym mogłoby dojść do pojedynczej instrukcji, w rzeczywistości uczyniłoby definicję języka bardziej złożoną .

Massimo
źródło
Ktoś właśnie podniósł tę odpowiedź (po 9 latach), która w rzeczywistości nie była konkretną odpowiedzią na to pytanie, ale o wiele bardziej ogólną dyskusją. Być może został on zastąpiony przez język i biblioteki… Ale i tak była to przyjemność, dziękuję.
Massimo
1

Ponieważ C ++ (i java) zezwalały na bloki kodu bez poprzedzającej instrukcji.

C ++ pozwolił im na to, ponieważ zrobił to C.

Można powiedzieć, że wszystko sprowadza się do tego, że zwyciężył projekt w amerykańskim języku programowania (oparty na języku C), a nie w europejskim języku programowania (w oparciu o Modula-2 ).

(Instrukcje sterujące działają na pojedynczą instrukcję, instrukcje mogą być grupami, aby tworzyć nowe instrukcje)

Ian Ringrose
źródło
Masz na myśli Module2 czy Modula2?
Richard Ev
Rzeczywiście, to jest poprawna odpowiedź. Dalej odpowiadałbym tylko: „Ponieważ tak jest w każdym języku komputerowym”.
Fattie
1
// if (a == b)
// if (a != b)
{
    // do something
}
user202448
źródło
0

1 Ponieważ ... Utrzymuje obszar zakresu instrukcji ... lub funkcję, jest to naprawdę przydatne do zarządzania dużym kodem.

{
    {
        // Here this 'i' is we can use for this scope only and out side of this scope we can't get this 'i' variable.

        int i = 0;
    }

    {
        int i = 0;
    }
}

wprowadź opis obrazu tutaj

Raj
źródło
0

Zapytałeś „dlaczego” C # zezwala na bloki kodu bez poprzedzających instrukcji. Pytanie „dlaczego” można również interpretować jako „jakie byłyby możliwe korzyści z tego konstruktu?”

Osobiście używam bloków kodu bez instrukcji w C #, gdzie czytelność jest znacznie poprawiona dla innych programistów, pamiętając jednocześnie, że blok kodu ogranicza zakres zmiennych lokalnych. Weźmy na przykład pod uwagę następujący fragment kodu, który jest znacznie łatwiejszy do odczytania dzięki dodatkowym blokom kodu:

OrgUnit world = new OrgUnit() { Name = "World" };
{
    OrgUnit europe = new OrgUnit() { Name = "Europe" };
    world.SubUnits.Add(europe);
    {
        OrgUnit germany = new OrgUnit() { Name = "Germany" };
        europe.SubUnits.Add(germany);

        //...etc.
    }
}
//...commit structure to DB here

Zdaję sobie sprawę, że można to rozwiązać bardziej elegancko, stosując metody dla każdego poziomu konstrukcji. Ale z drugiej strony pamiętaj, że takie rzeczy, jak przykładowe serwery danych, zwykle muszą być szybkie.

Tak więc, mimo że powyższy kod jest wykonywany liniowo, struktura kodu reprezentuje strukturę obiektów w „świecie rzeczywistym”, ułatwiając w ten sposób innym programistom zrozumienie, utrzymanie i rozszerzenie.

Marco
źródło