W C # i Javie (i być może także w innych językach) zmienne zadeklarowane w bloku „try” nie znajdują się w zakresie w odpowiednich blokach „catch” lub „final”. Na przykład poniższy kod nie jest kompilowany:
try {
String s = "test";
// (more code...)
}
catch {
Console.Out.WriteLine(s); //Java fans: think "System.out.println" here instead
}
W tym kodzie błąd kompilacji występuje w odwołaniu do s w bloku catch, ponieważ s znajduje się tylko w zakresie w bloku try. (W Javie błąd kompilacji to „s nie można rozwiązać”; w C # to „nazwa„ s ”nie istnieje w bieżącym kontekście”).
Wydaje się, że ogólnym rozwiązaniem tego problemu jest deklarowanie zmiennych tuż przed blokiem try, a nie w bloku try:
String s;
try {
s = "test";
// (more code...)
}
catch {
Console.Out.WriteLine(s); //Java fans: think "System.out.println" here instead
}
Jednak przynajmniej dla mnie (1) wydaje się to niezgrabnym rozwiązaniem i (2) skutkuje tym, że zmienne mają większy zakres niż zamierzał programista (cała pozostała część metody, a nie tylko w kontekście spróbuj złapać wreszcie).
Moje pytanie brzmi: jakie były / są uzasadnienie (y) tej decyzji dotyczącej projektu języka (w Javie, w C # i / lub w innych odpowiednich językach)?
Tradycyjnie w językach w stylu C to, co dzieje się w nawiasach klamrowych, pozostaje wewnątrz nawiasów klamrowych. Myślę, że posiadanie czasu życia zmiennej rozciągającej się w takich zakresach byłoby nieintuicyjne dla większości programistów. Możesz osiągnąć to, co chcesz, umieszczając bloki try / catch / final wewnątrz innego poziomu nawiasów. na przykład
EDIT: Myślę, że każda reguła ma mieć wyjątek. Oto poprawne C ++:
Zakres x jest warunkowy, klauzula then i klauzula else.
źródło
Wszyscy inni poruszyli podstawy - to, co dzieje się w bloku, pozostaje w bloku. Ale w przypadku .NET pomocne może być zbadanie, co według kompilatora się dzieje. Weźmy na przykład następujący kod try / catch (zwróć uwagę, że StreamReader jest poprawnie zadeklarowany poza blokami):
Spowoduje to kompilację do czegoś podobnego do następującego w MSIL:
Co widzimy MSIL szanuje bloki - są one nieodłączną częścią kodu źródłowego generowanego podczas kompilowania C #. Zakres jest nie tylko sztywny w specyfikacji C #, ale także w specyfikacji CLR i CLS.
Luneta Cię chroni, ale czasami trzeba ją obejść. Z biegiem czasu przyzwyczajasz się do tego i zaczyna być to naturalne. Jak wszyscy mówili, to, co dzieje się w bloku, pozostaje w tym bloku. Chcesz się czymś podzielić? Musisz wyjść poza bloki ...
źródło
W każdym razie w C ++ zakres zmiennej automatycznej jest ograniczony przez otaczające ją nawiasy klamrowe. Dlaczego ktoś miałby oczekiwać, że będzie inaczej, umieszczając słowo kluczowe try poza nawiasami klamrowymi?
źródło
Jak zauważył ravenspoint, każdy oczekuje, że zmienne będą lokalne dla bloku, w którym są zdefiniowane.
try
Wprowadza blok i tak samocatch
.Jeśli chcesz, aby zmienne były lokalne dla obu
try
icatch
, spróbuj umieścić oba w bloku:źródło
Prosta odpowiedź jest taka, że język C i większość języków, które odziedziczyły jego składnię, ma zasięg blokowy. Oznacza to, że jeśli zmienna jest zdefiniowana w jednym bloku, tj. Wewnątrz {}, to jest to jej zasięg.
Nawiasem mówiąc, wyjątkiem jest JavaScript, który ma podobną składnię, ale ma zakres funkcji. W JavaScript zmienna zadeklarowana w bloku try znajduje się w zakresie w bloku catch, a wszędzie indziej w funkcji zawierającej.
źródło
@burkhard ma pytanie, dlaczego odpowiedział poprawnie, ale jako uwaga, którą chciałem dodać, chociaż zalecany przykład rozwiązania jest dobry 99,9999 +% czasu, nie jest to dobra praktyka, znacznie bezpieczniej jest sprawdzić wartość null przed użyciem coś tworzy instancję w bloku try lub inicjalizuje zmienną do czegoś, zamiast deklarować ją przed blokiem try. Na przykład:
Lub:
Powinno to zapewnić skalowalność obejścia, tak że nawet jeśli to, co robisz w bloku try jest bardziej złożone niż przypisywanie ciągu, powinieneś być w stanie bezpiecznie uzyskać dostęp do danych z bloku catch.
źródło
Zgodnie z sekcją zatytułowaną „Jak rzucać i łapać wyjątki” w lekcji 2 zestawu MCTS Self-Paced Training Kit (egzamin 70-536): Microsoft® .NET Framework 2.0 - Application Development Foundation , przyczyną może być wyjątek przed deklaracjami zmiennych w bloku try (jak już zauważyli inni).
Cytat ze strony 25:
„Zauważ, że deklaracja StreamReader została przeniesiona poza blok Try w poprzednim przykładzie. Jest to konieczne, ponieważ blok Finalnie nie może uzyskać dostępu do zmiennych zadeklarowanych w bloku Try. Ma to sens, ponieważ w zależności od miejsca wystąpienia wyjątku deklaracje zmiennych w Blok próbny mógł jeszcze nie zostać wykonany . "
źródło
Odpowiedź, jak wszyscy zauważyli, brzmi właściwie „tak definiuje się bloki”.
Istnieje kilka propozycji, aby uczynić kod ładniejszym. Zobacz ARM
Zamknięcia mają również rozwiązać ten problem.
AKTUALIZACJA: ARM jest zaimplementowany w Javie 7. http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html
źródło
Twoje rozwiązanie jest dokładnie tym, co powinieneś zrobić. Nie możesz być pewien, że Twoja deklaracja została osiągnięta w bloku try, co spowodowałoby kolejny wyjątek w bloku catch.
Po prostu musi działać jako oddzielne zakresy.
źródło
Zmienne są na poziomie bloku i ograniczone do tego bloku Try lub Catch. Podobny do definiowania zmiennej w instrukcji if. Pomyśl o tej sytuacji.
Ciąg nigdy nie zostałby zadeklarowany, więc nie można na nim polegać.
źródło
Ponieważ blok try i blok catch to 2 różne bloki.
Czy w poniższym kodzie spodziewasz się, że s zdefiniowane w bloku A będą widoczne w bloku B?
źródło
Podczas gdy w twoim przykładzie dziwne jest, że nie działa, weź ten podobny:
Spowodowałoby to, że catch zgłosiłby wyjątek odwołania zerowego, gdyby kod 1 zepsuł. Chociaż semantyka try / catch jest całkiem dobrze zrozumiana, byłby to irytujący przypadek narożny, ponieważ s jest zdefiniowane z wartością początkową, więc teoretycznie nigdy nie powinno być zerowe, ale w ramach semantyki współdzielonej tak byłoby.
Teoretycznie można to naprawić, zezwalając tylko na oddzielne definicje (
String s; s = "1|2";
) lub inny zestaw warunków, ale generalnie łatwiej jest po prostu powiedzieć nie.Ponadto umożliwia globalne definiowanie semantyki zakresu bez wyjątku, a konkretnie
{}
, we wszystkich przypadkach , wartości lokalne trwają tak długo, jak długo są zdefiniowane w programie. Drobny punkt, ale punkt.Wreszcie, aby zrobić to, co chcesz, możesz dodać zestaw nawiasów wokół try catch. Daje żądany zakres, chociaż kosztuje trochę czytelności, ale nie za dużo.
źródło
W konkretnym przykładzie, który podałeś, inicjalizacja s nie może zgłosić wyjątku. Można by więc pomyśleć, że może jego zakres mógłby zostać rozszerzony.
Ale ogólnie wyrażenia inicjatora mogą generować wyjątki. Nie miałoby sensu, gdyby zmienna, której inicjator zgłosił wyjątek (lub która została zadeklarowana po innej zmiennej, w której to się zdarzyło) znajdowała się w zasięgu catch / final.
Ucierpiałaby również czytelność kodu. Reguła w C (i językach, które za nią podążają, w tym C ++, Java i C #) jest prosta: zakresy zmiennych podążają za blokami.
Jeśli chcesz, aby zmienna znajdowała się w zakresie dla try / catch / w końcu, ale nigdzie indziej, zawiń całość w inny zestaw nawiasów klamrowych (czysty blok) i zadeklaruj zmienną przed try.
źródło
Jednym z powodów, dla których nie znajdują się w tym samym zakresie, jest to, że w dowolnym momencie bloku try możesz zgłosić wyjątek. Gdyby znajdowały się w tym samym zakresie, czekałaby katastrofa, ponieważ w zależności od tego, gdzie został zgłoszony wyjątek, może być jeszcze bardziej niejednoznaczny.
Przynajmniej wtedy, gdy jest zadeklarowana poza blokiem try, wiesz na pewno, jaka zmienna może być przynajmniej w przypadku zgłoszenia wyjątku; Wartość zmiennej przed blokiem try.
źródło
Kiedy deklarujesz zmienną lokalną, jest ona umieszczana na stosie (dla niektórych typów cała wartość obiektu będzie na stosie, dla innych tylko odniesienie będzie na stosie). Gdy w bloku try występuje wyjątek, zmienne lokalne w bloku są zwalniane, co oznacza, że stos jest „rozwijany” z powrotem do stanu, w którym znajdował się na początku bloku try. Jest to zgodne z projektem. W ten sposób try / catch jest w stanie wycofać się ze wszystkich wywołań funkcji w bloku i przywrócić system do stanu funkcjonalnego. Bez tego mechanizmu nigdy nie można było być pewnym stanu czegokolwiek w przypadku wystąpienia wyjątku.
Posiadanie kodu obsługi błędów polegającego na zewnętrznie zadeklarowanych zmiennych, których wartości zostały zmienione w bloku try, wydaje mi się złym projektem. To, co robisz, to celowe wyciekanie zasobów w celu uzyskania informacji (w tym konkretnym przypadku nie jest tak źle, ponieważ tylko wyciekasz informacje, ale wyobraź sobie, że to był jakiś inny zasób? Po prostu utrudniasz sobie życie w przyszłość). Sugerowałbym podzielenie bloków try na mniejsze fragmenty, jeśli potrzebujesz większej szczegółowości w obsłudze błędów.
źródło
Kiedy masz próbę złapania, powinieneś w większości wiedzieć, że może to spowodować błędy. Te klasy wyjątków normalnie informują wszystko, czego potrzebujesz o wyjątku. Jeśli nie, powinieneś stworzyć własne klasy wyjątków i przekazać te informacje. W ten sposób nigdy nie będziesz musiał pobierać zmiennych z wnętrza bloku try, ponieważ wyjątek nie wymaga wyjaśnień. Więc jeśli musisz to dużo zrobić, pomyśl o swoim projekcie i spróbuj pomyśleć, czy jest jakiś inny sposób, że możesz przewidzieć nadchodzące wyjątki lub użyć informacji pochodzących z wyjątków, a następnie może ponownie wrzucić własne wyjątek z dodatkowymi informacjami.
źródło
Jak zauważyli inni użytkownicy, nawiasy klamrowe definiują zakres w prawie każdym języku C, jaki znam.
Jeśli jest to prosta zmienna, to dlaczego obchodzi Cię, jak długo będzie ona objęta zakresem? To nie jest taka wielka sprawa.
w C #, jeśli jest to zmienna złożona, będziesz chciał zaimplementować IDisposable. Następnie możesz użyć try / catch / final i wywołać obj.Dispose () w bloku last. Możesz też użyć słowa kluczowego using, które automatycznie wywoła metodę Dispose na końcu sekcji kodu.
źródło
W Pythonie są one widoczne w blokach catch / final jeśli linia deklarująca, że nie została wyrzucona.
źródło
Co się stanie, jeśli wyjątek zostanie zgłoszony w jakimś kodzie, który znajduje się nad deklaracją zmiennej. Co oznacza, że sama deklaracja nie miała miejsca w tym przypadku.
źródło
C # Spec (15.2) stwierdza: „Zakres zmiennej lokalnej lub stałe zadeklarowane w bloku ist bloku.”
(w pierwszym przykładzie blok try to blok, w którym zadeklarowano „s”)
źródło
Pomyślałem, że ponieważ coś w bloku try wyzwoliło wyjątek, nie można ufać zawartości przestrzeni nazw - tj. Odwoływanie się do ciągu znaków w bloku catch mogłoby spowodować wyrzucenie kolejnego wyjątku.
źródło
Cóż, jeśli nie zgłosi błędu kompilacji i możesz go zadeklarować do końca metody, nie byłoby sposobu, aby zadeklarować go tylko w zakresie try. Zmusza cię do wyraźnego określenia, gdzie zmienna ma istnieć i nie przyjmuje założeń.
źródło
Jeśli przez chwilę zignorujemy kwestię blokowania zakresu, osoba udzielająca pomocy będzie musiała pracować znacznie ciężej w sytuacji, która nie jest dobrze zdefiniowana. Chociaż nie jest to niemożliwe, błąd określania zakresu zmusza również Ciebie, autora kodu, do uświadomienia sobie implikacji kodu, który piszesz (że ciąg znaków s może być pusty w bloku catch). Jeśli twój kod był legalny, w przypadku wyjątku OutOfMemory, s nie ma nawet gwarancji, że zostanie przydzielone miejsce w pamięci:
CLR (a tym samym kompilator) również wymusza inicjalizację zmiennych przed ich użyciem. W przedstawionym bloku catch nie może tego zagwarantować.
W rezultacie kompilator musi wykonać dużo pracy, co w praktyce nie zapewnia wielu korzyści i prawdopodobnie wprowadzałoby ludzi w błąd i prowadziłoby ich do pytania, dlaczego try / catch działa inaczej.
Oprócz spójności, nie dopuszczając niczego wymyślnego i zgodnie z już ustaloną semantyką określania zakresu używaną w całym języku, kompilator i środowisko CLR są w stanie zapewnić większą gwarancję stanu zmiennej wewnątrz bloku catch. Że istnieje i został zainicjowany.
Zwróć uwagę, że projektanci języka wykonali dobrą robotę z innymi konstrukcjami, takimi jak używanie i blokowanie, w których problem i zakres są dobrze zdefiniowane, co pozwala napisać jaśniejszy kod.
np. słowo kluczowe using z obiektami IDisposable w:
jest równa:
Jeśli twoja próba / złapanie / w końcu jest trudna do zrozumienia, spróbuj refaktoryzacji lub wprowadzenia innej warstwy pośredniej z klasą pośrednią, która zawiera semantykę tego, co próbujesz osiągnąć. Nie widząc prawdziwego kodu, trudno jest być bardziej szczegółowym.
źródło
Zamiast zmiennej lokalnej można by zadeklarować właściwość publiczną; Powinno to również uniknąć kolejnego potencjalnego błędu nieprzypisanej zmiennej. public string S {get; zestaw; }
źródło
Jeśli operacja przypisania nie powiedzie się, instrukcja catch będzie miała odwołanie o wartości null z powrotem do nieprzypisanej zmiennej.
źródło
C # 3.0:
źródło