C # pozwala mi wykonać następujące czynności (przykład z MSDN):
using (Font font3 = new Font("Arial", 10.0f),
font4 = new Font("Arial", 10.0f))
{
// Use font3 and font4.
}
Co się stanie, jeśli font4 = new Font
wyrzuci? Z tego co rozumiem, font3 będzie wyciekać zasoby i nie zostanie usunięty.
- Czy to prawda? (font4 nie zostanie usunięty)
- Czy to oznacza, że
using(... , ...)
należy całkowicie unikać na korzyść stosowania zagnieżdżonego?
c#
using
using-statement
Benjamin Gruenbaum
źródło
źródło
using(... , ...)
był kompilowany do zagnieżdżonych bloków, niezależnie od tego, ale nie wiem tego na pewno.using
, GC ostatecznie go odbierze.finally
bloku, nie wszedłby do bloku, dopóki wszystkie zasoby nie zostałyby skonstruowane.using
natry
-finally
, wyrażenie inicjalizacji jest oceniane pozatry
. Jest to więc rozsądne pytanie.Odpowiedzi:
Nie.
Kompilator wygeneruje oddzielny
finally
blok dla każdej zmiennej.Spec (§8.13) mówi:
źródło
AKTUALIZACJA : Użyłem tego pytania jako podstawy do artykułu, który można znaleźć tutaj ; zobacz to, aby uzyskać dodatkowe omówienie tego problemu. Dzięki za dobre pytanie!
Chociaż odpowiedź Schabse jest oczywiście poprawna i odpowiada na zadane pytanie, istnieje ważny wariant twojego pytania, którego nie zadałeś:
Pozwólcie, że wyjaśnię to trochę. Załóżmy, że mamy:
Teraz mamy
To jest to samo co
DOBRZE. Załóżmy, że
Whatever
rzuca. Następniefinally
blok jest uruchamiany i zasób jest zwalniany. Nie ma problemu.Załóżmy, że
Blah1()
rzuca. Następnie rzut ma miejsce, zanim zasób zostanie przydzielony. Obiekt został przydzielony, ale ctor nigdy nie wraca, więcfoo
nigdy nie jest wypełniany. Nigdy nietry
weszliśmy do pola, więc nigdy nie wchodzimy dofinally
żadnego. Odniesienie do obiektu zostało osierocone. W końcu GC to wykryje i umieści to w kolejce finalizatora.handle
nadal wynosi zero, więc finalizator nic nie robi. Zwróć uwagę, że finalizator musi być niezawodny w obliczu finalizowanego obiektu, którego konstruktor nigdy nie został ukończony . Jesteś zobowiązany napisać finalizatory że są to silne. To kolejny powód, dla którego powinieneś pozostawić pisanie finalizatorów ekspertom i nie próbować robić tego samodzielnie.Załóżmy, że
Blah3()
rzuca. Rzut następuje po przydzieleniu zasobu. Ale znowu,foo
nigdy nie jest wypełniony, nigdy nie wchodzimy dofinally
, a obiekt jest czyszczony przez wątek finalizatora. Tym razem uchwyt jest niezerowy, a finalizator czyści go. Ponownie finalizator działa na obiekcie, którego konstruktor nigdy się nie powiódł, ale finalizator i tak działa. Oczywiście musiało, bo tym razem miało do wykonania pracę.Teraz załóżmy, że
Blah2()
rzuca. Rzut następuje po przydzieleniu zasobu, ale przedhandle
wypełnieniem! Ponownie finalizator będzie działał, ale terazhandle
nadal wynosi zero i wyciekamy uchwyt!Musisz napisać niezwykle sprytny kod, aby zapobiec wystąpieniu tego wycieku. Teraz, w przypadku twojego
Font
zasobu, kogo to obchodzi? Wyciekamy uchwyt czcionki, wielka sprawa. Ale jeśli absolutnie stanowczo wymagasz, aby każdy niezarządzany zasób został wyczyszczony, bez względu na czas wyjątków , masz bardzo trudny problem.CLR musi rozwiązać ten problem za pomocą zamków. Od czasu C # 4 blokady, które używają
lock
instrukcji, zostały zaimplementowane w następujący sposób:Enter
został bardzo starannie napisany, aby bez względu na to, jakie wyjątki zostały wyrzucone ,lockEntered
jest ustawiony na wartość true wtedy i tylko wtedy, gdy blokada została faktycznie podjęta. Jeśli masz podobne wymagania, to tak naprawdę musisz napisać:i
AllocateResource
sprytnie piszMonitor.Enter
tak, że bez względu na to, co dzieje się w środkuAllocateResource
, polehandle
jest wypełnione wtedy i tylko wtedy, gdy trzeba je cofnąć.Opis technik służących do tego wykracza poza zakres tej odpowiedzi. Skonsultuj się z ekspertem, jeśli masz takie wymaganie.
źródło
Blah
wywołania metod. Co powstrzymuje wystąpienie wyjątku ThreadAbortException w którymkolwiek z tych punktów?AllocateResource
ale przed przypisaniem dox
. WThreadAbortException
tym momencie może się wydarzyć. Wydaje się, że wszystkim tutaj brakuje mojego punktu widzenia, jakim jest stworzenie zasobu i przypisanie do niego odniesienia do zmiennej nie jest operacją atomową . Aby rozwiązać problem, który zidentyfikowałem, musisz uczynić z tego operację atomową.Jako uzupełnienie odpowiedzi @SLaks, oto IL dla twojego kodu:
Zwróć uwagę na zagnieżdżone bloki try / final.
źródło
Ten kod (na podstawie oryginalnej próbki):
Tworzy następujący CIL (w programie Visual Studio 2013 , przeznaczony dla .NET 4.5.1):
Jak widać,
try {}
blok zaczyna się dopiero po pierwszej alokacji, która ma miejsce o godzIL_0012
. Na pierwszy rzut oka wydaje się, że przydziela to pierwszy element w niezabezpieczonym kodzie. Należy jednak zauważyć, że wynik jest przechowywany w lokalizacji 0. Jeśli następnie druga alokacja nie powiedzie się, wykonywany jest blok zewnętrznyfinally {}
, a to pobiera obiekt z lokalizacji 0, tj. Pierwszej alokacjifont3
, i wywołuje jegoDispose()
metodę.Co ciekawe, dekompilacja tego zestawu za pomocą dotPeek daje następujące odtworzone źródło:
Zdekompilowany kod potwierdza, że wszystko jest poprawne i że
using
jest zasadniczo rozszerzony do zagnieżdżonych plikówusing
s. Kod CIL jest trochę zagmatwany i musiałem się w niego wpatrywać przez dobre kilka minut, zanim właściwie zrozumiałem, co się dzieje, więc nie jestem zaskoczony, że zaczęły się pojawiać `` opowieści starych żon '' to. Jednak wygenerowany kod jest niepodważalną prawdą.źródło
Oto przykładowy kod potwierdzający odpowiedź @SLaks:
źródło
font4 = new Font
wyrzuci? Z tego, co rozumiem, font3 spowoduje wyciek zasobów i nie zostanie usunięty”.