C # 2008
Pracuję nad tym od dłuższego czasu i nadal jestem zdezorientowany co do użycia metod finalizacji i usuwania w kodzie. Moje pytania są poniżej:
Wiem, że potrzebujemy tylko finalizatora, który pozbywa się niezarządzanych zasobów. Jeśli jednak istnieją zasoby zarządzane, które wykonują połączenia z zasobami niezarządzanymi, czy nadal będzie musiał zaimplementować finalizator?
Jeśli jednak opracuję klasę, która nie korzysta z żadnego niezarządzanego zasobu - bezpośrednio lub pośrednio, czy powinienem zaimplementować
IDisposable
opcję, aby umożliwić klientom tej klasy korzystanie z „instrukcji using”?Czy byłoby możliwe zaimplementowanie IDisposable, aby umożliwić klientom twojej klasy korzystanie z instrukcji using?
using(myClass objClass = new myClass()) { // Do stuff here }
Opracowałem ten prosty kod poniżej, aby zademonstrować użycie finalizacji / usuwania:
public class NoGateway : IDisposable { private WebClient wc = null; public NoGateway() { wc = new WebClient(); wc.DownloadStringCompleted += wc_DownloadStringCompleted; } // Start the Async call to find if NoGateway is true or false public void NoGatewayStatus() { // Start the Async's download // Do other work here wc.DownloadStringAsync(new Uri(www.xxxx.xxx)); } private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { // Do work here } // Dispose of the NoGateway object public void Dispose() { wc.DownloadStringCompleted -= wc_DownloadStringCompleted; wc.Dispose(); GC.SuppressFinalize(this); } }
Pytanie o kod źródłowy:
Tutaj nie dodałem finalizatora i zwykle finalizator zostanie wywołany przez GC, a finalizator wywoła Dispose. Ponieważ nie mam finalizatora, kiedy wywołać metodę Dispose? Czy to klient klasy musi to nazwać?
Tak więc moja klasa w tym przykładzie nazywa się NoGateway, a klient może użyć i usunąć klasę w następujący sposób:
using(NoGateway objNoGateway = new NoGateway()) { // Do stuff here }
Czy metoda Dispose byłaby wywoływana automatycznie, gdy wykonanie osiągnie koniec używanego bloku, czy też klient musi ręcznie wywołać metodę dispose? to znaczy
NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it
Korzystam z
WebClient
klasy w mojejNoGateway
klasie. PonieważWebClient
implementujeIDisposable
interfejs, czy oznacza to, żeWebClient
pośrednio wykorzystuje niezarządzane zasoby? Czy istnieje twarda i szybka zasada, aby się do tego stosować? Skąd mam wiedzieć, że klasa korzysta z niezarządzanych zasobów?
źródło
Odpowiedzi:
Zalecany wzór IDisposable znajduje się tutaj . Podczas programowania klasy korzystającej z IDisposable, ogólnie powinieneś używać dwóch wzorców:
Podczas implementowania klasy zapieczętowanej, która nie korzysta z niezarządzanych zasobów, wystarczy zaimplementować metodę Dispose, jak w przypadku normalnych implementacji interfejsu:
Wdrażając niezamkniętą klasę, wykonaj następujące czynności:
Zauważ, że nie zadeklarowałem finalizatora
B
; powinieneś wdrożyć finalizator tylko wtedy, gdy masz do dyspozycji rzeczywiste niezarządzane zasoby. CLR traktuje obiekty finalizowalne inaczej niż obiekty finalizowalne, nawet jeśliSuppressFinalize
jest wywoływany.Nie powinieneś więc zadeklarować finalizatora, chyba że musisz, ale dajesz spadkobiercom swojej klasy haczyk, aby zadzwonić do ciebie
Dispose
i sami zaimplementować finalizator, jeśli bezpośrednio używają niezarządzanych zasobów:Jeśli nie używasz bezpośrednio niezarządzanych zasobów (
SafeHandle
a znajomi się nie liczą, ponieważ deklarują własne finalizatory), nie implementuj finalizatora, ponieważ GC inaczej traktuje klasy finalizowane, nawet jeśli później stłumisz finalizator. Zauważ też, że mimo żeB
nie ma finalizatora, nadal wywołujeSuppressFinalize
prawidłowe działanie z podklasami, które implementują finalizator.Kiedy klasa implementuje interfejs IDisposable, oznacza to, że gdzieś istnieją pewne niezarządzane zasoby, których należy się pozbyć po zakończeniu korzystania z klasy. Rzeczywiste zasoby są zawarte w klasach; nie musisz ich jawnie usuwać. Wystarczy zadzwonić
Dispose()
lub zawinąć klasę wusing(...) {}
, aby upewnić się, że wszelkie niezarządzane zasoby zostaną usunięte w razie potrzeby.źródło
IDisposable
, są szanse, że i tak pozostanie na jakiś czas. Zapisujesz CLR, starając się skopiować go z Gen0 -> Gen1 -> Gen2IDisposable
Trudno zrozumieć oficjalny wzorzec wdrożenia . Uważam, że ten jest lepszy :Nawet lepsze rozwiązanie ma mieć regułę, którą zawsze trzeba utworzyć klasy otoki dla każdego zasobu niekontrolowana, że trzeba uchwycie:
W przypadku
SafeHandle
tych pochodnych klasy te powinny być bardzo rzadkie .Wynik dla klas jednorazowych, które nie zajmują się bezpośrednio niezarządzanymi zasobami, nawet w przypadku dziedziczenia, jest potężny: nie muszą się już martwić o niezarządzane zasoby . Będą łatwe do wdrożenia i zrozumienia:
źródło
disposed
flagę i odpowiednio sprawdzić.disposed
flagę, sprawdź ją przed wyrzuceniem i ustaw po wyrzuceniu. Poszukaj tutaj pomysłu. Powinieneś również sprawdzić flagę przed jakąkolwiek metodą klasy. Ma sens? Czy to skomplikowane?Zauważ, że każda implementacja IDisposable powinna być zgodna z poniższym wzorcem (IMHO). Opracowałem ten wzorzec na podstawie informacji od kilku doskonałych „bogów” .NET w Wytycznych projektowych .NET Framework (zauważ, że MSDN z jakiegoś powodu tego nie przestrzega!). Wytyczne dotyczące projektowania .NET Framework zostały napisane przez Krzysztofa Cwalinę (wówczas architekt CLR) i Brada Abramsa (uważam wówczas, że kierownika programu CLR) oraz Billa Wagnera ([Effective C #] i [More Effective C #] (po prostu weź poszukaj ich na Amazon.com:
Należy pamiętać, że NIGDY nie należy implementować finalizatora, chyba że klasa zawiera bezpośrednio (nie dziedziczy) zasoby niezarządzane. Po wdrożeniu finalizatora w klasie, nawet jeśli nigdy nie jest on wywoływany, gwarantujemy, że przeżyjesz dla dodatkowej kolekcji. Jest on automatycznie umieszczany w kolejce finalizacji (która działa w jednym wątku). Ponadto, jedna bardzo ważna uwaga ... cały kod wykonywany w finalizatorze (jeśli trzeba go zaimplementować) MUSI być bezpieczny dla wątków I bezpieczny dla wyjątków! ZŁE rzeczy dzieją się inaczej ... (tj. Nieokreślone zachowanie, aw przypadku wyjątku fatalna, niemożliwa do odzyskania awaria aplikacji).
Wzorzec, który utworzyłem (i napisałem fragment kodu) jest następujący:
Oto kod implementujący IDisposable w klasie pochodnej. Zauważ, że nie musisz jawnie wymieniać dziedziczenia po IDisposable w definicji klasy pochodnej.
Opublikowałem tę implementację na swoim blogu pod adresem: Jak prawidłowo wdrożyć wzorzec usuwania
źródło
Interlocked.Exchange
na liczbie całkowitejIsDisposed
w nie-wirtualnej funkcji opakowania byłoby bezpieczniejsze.Zgadzam się z pm100 (i powinienem to wyraźnie powiedzieć w moim wcześniejszym poście).
Nigdy nie powinieneś implementować IDisposable w klasie, chyba że jest to potrzebne. Mówiąc konkretnie, jest około 5 razy, kiedy kiedykolwiek będziesz potrzebować / powinieneś wdrożyć IDisposable:
Twoja klasa wyraźnie zawiera (tj. Nie poprzez dziedziczenie) wszelkie zasoby zarządzane, które implementują IDisposable i powinny zostać wyczyszczone, gdy klasa nie będzie już używana. Na przykład, jeśli twoja klasa zawiera instancję Stream, DbCommand, DataTable itp.
Twoja klasa zawiera jawnie wszelkie zarządzane zasoby, które implementują metodę Close () - np. IDataReader, IDbConnection, itp. Zauważ, że niektóre z tych klas implementują IDisposable poprzez użycie Dispose (), a także metody Close ().
Twoja klasa zawiera jawnie niezarządzany zasób - np. Obiekt COM, wskaźniki (tak, możesz używać wskaźników w zarządzanym C #, ale muszą one być zadeklarowane w „niebezpiecznych” blokach itp. W przypadku zasobów niezarządzanych powinieneś również upewnić się, że Wywołaj System.Runtime.InteropServices.Marshal.ReleaseComObject () na RCW. Chociaż RCW jest teoretycznie zarządzanym opakowaniem, nadal podliczanie referencji trwa.
Jeśli Twoja klasa subskrybuje wydarzenia przy użyciu silnych referencji. Musisz wyrejestrować się / odłączyć od wydarzeń. Zawsze upewnij się, że nie są one najpierw zerowe, zanim spróbujesz je wyrejestrować / odłączyć !.
Twoja klasa zawiera dowolną kombinację powyższych ...
Zalecaną alternatywą do pracy z obiektami COM i konieczności użycia Marshal.ReleaseComObject () jest użycie klasy System.Runtime.InteropServices.SafeHandle.
BCL (Zespół biblioteki klas podstawowych) ma dobry post na ten temat tutaj http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx
Jedną z bardzo ważnych uwag jest to, że jeśli pracujesz z WCF i czyścisz zasoby, powinieneś ZAWSZE unikać ZAWSZE bloku. Istnieje wiele postów na blogu i niektóre w witrynie MSDN o tym, dlaczego jest to zły pomysł. Napisałem również o tym tutaj - nie używaj „using ()” z proxy WCF
źródło
Używanie lambdów zamiast IDisposable.
Nigdy nie byłem zachwycony całym pomysłem użycia / IDisposable. Problem polega na tym, że dzwoniący musi:
Moją nową preferowaną metodą jest użycie metody fabrycznej i lambda
Wyobraź sobie, że chcę zrobić coś za pomocą SqlConnection (coś, co powinno być zapakowane w use). Klasycznie byś zrobił
Nowy sposób
W pierwszym przypadku osoba dzwoniąca nie mogła po prostu użyć składni using. W drugim przypadku użytkownik nie ma wyboru. Nie ma metody, która tworzy obiekt SqlConnection, wywołujący musi wywołać DoWithConnection.
DoWithConnection wygląda następująco
MakeConnection
jest teraz prywatnyźródło
DoForAll(Action<T>) where T:IComparable<T>
, wywołując wskazanego delegata na każdym rekordzie. Biorąc pod uwagę dwa takie obiekty, z których oba zwrócą dane w posortowanej kolejności, w jaki sposób jeden wynik wszystkich elementów, które istnieją w jednej kolekcji, ale nie w drugim? Jeśli typy są zaimplementowaneIEnumerable<T>
, można wykonać operację scalania, ale to nie zadziałaDoForAll
.DoForAll
kolekcji bez konieczności kopiowania jednego zbioru w całości do jakiejś innej struktury, byłoby użycie dwóch wątków, które byłyby bardziej pochopne z zasobów niż po prostu użycie kilku IEnumerable i ostrożność aby je zwolnić.nikt nie odpowiedział na pytanie, czy powinieneś wdrożyć IDisposable, nawet jeśli go nie potrzebujesz.
Krótka odpowiedź: nie
Długa odpowiedź:
Umożliwiłoby to konsumentowi z twojej klasy korzystanie z „korzystania”. Pytanie, które zadam, brzmi - dlaczego mieliby to zrobić? Większość deweloperów nie będzie używać „używania”, chyba że wie, że musi - i skąd to wie. Zarówno
Tak więc, implementując IDisposable, mówisz programistom (przynajmniej niektórym), że ta klasa zawiera coś, co musi zostać wydane. Będą używać „using” - ale są inne przypadki, w których użycie nie jest możliwe (zakres obiektu nie jest lokalny); i w tych innych przypadkach będą musieli martwić się o żywotność przedmiotów - na pewno bym się martwił. Ale to nie jest konieczne
Wdrażasz Idisposable, aby umożliwić im używanie, ale nie będą używać, chyba że im to powiesz.
Więc nie rób tego
źródło
Jeśli korzystasz z innych zarządzanych obiektów korzystających z niezarządzanych zasobów, nie jest obowiązkiem upewnić się, że zostały one sfinalizowane. Twoim obowiązkiem jest wywołać Dispose na tych obiektach, gdy Dispose zostanie wywołany na twoim obiekcie i na tym się skończy.
Jeśli twoja klasa nie używa żadnych ograniczonych zasobów, nie rozumiem, dlaczego miałbyś uczynić swoją klasę implementacją IDisposable. Powinieneś to zrobić tylko wtedy, gdy:
Tak, kod używający twojego kodu musi wywoływać metodę Dispose twojego obiektu. I tak, kod używający twojego obiektu może używać
using
tak, jak pokazano.(Ponownie 2?) Prawdopodobnie WebClient używa zasobów niezarządzanych lub innych zasobów zarządzanych, które implementują IDisposable. Dokładny powód nie jest jednak ważny. Ważne jest to, że implementuje IDisposable, a więc to na tobie spoczywa obowiązek pozbycia się obiektu po zakończeniu pracy, nawet jeśli okaże się, że WebClient w ogóle nie korzysta z innych zasobów.
źródło
Usuń wzór:
Przykład dziedziczenia:
źródło
Niektóre aspekty innej odpowiedzi są nieco niepoprawne z 2 powodów:
Pierwszy,
faktycznie odpowiada:
Może to zabrzmieć śmiesznie, ponieważ „nowy” operator nigdy nie powinien zwracać „null”, chyba że masz wyjątek OutOfMemory. Ale rozważ następujące przypadki: 1. Wywołujesz FactoryClass, który zwraca zasób IDisposable lub 2. Jeśli masz typ, który może dziedziczyć po IDisposable, w zależności od jego implementacji - pamiętaj, że widziałem nieprawidłowo zaimplementowany wzorzec IDisposable wiele razy u wielu klientów, gdy programiści dodają po prostu metodę Dispose () bez dziedziczenia po IDisposable (źle, źle, źle). Może się również zdarzyć, że zasób IDisposable zostanie zwrócony z właściwości lub metody (ponownie źle, źle, źle - nie „rozdawaj zasobów IDisposable”)
Jeśli operator „as” zwróci null (lub właściwość lub metoda zwracająca zasób), a kod w bloku „using” chroni przed „null”, kod nie wybuchnie podczas próby wywołania Dispose na obiekcie null z powodu „wbudowana” kontrola zerowa.
Drugim powodem, dla którego twoja odpowiedź jest nieprawidłowa, jest następujący:
Po pierwsze, finalizacja (podobnie jak sama GC) jest niedeterministyczna. CLR określa, kiedy zadzwoni do finalizatora. tzn. programista / kod nie ma pojęcia. Jeśli wzorzec IDisposable jest poprawnie zaimplementowany (jak napisałem powyżej) i wywołano GC.SuppressFinalize (), finalizator NIE zostanie wywołany. Jest to jeden z głównych powodów prawidłowego wdrożenia wzorca. Ponieważ na zarządzany proces przypada tylko 1 wątek finalizatora, niezależnie od liczby procesorów logicznych, można łatwo obniżyć wydajność, wykonując kopię zapasową lub nawet zawieszając wątek finalizatora, zapominając o wywołaniu GC.SuppressFinalize ().
Na moim blogu opublikowałem poprawną implementację Wzorca usuwania: Jak prawidłowo wdrożyć Wzorzec usuwania
źródło
NoGateway = new NoGateway();
iNoGateway != null
?1) WebClient jest typem zarządzanym, więc nie potrzebujesz finalizatora. Finalizator jest potrzebny w przypadku, gdy użytkownicy nie usuwają () twojej klasy NoGateway, a rodzimy typ (który nie jest zbierany przez GC) musi zostać później wyczyszczony. W takim przypadku, jeśli użytkownik nie wywoła metody Dispose (), zawarta usługa WebClient zostanie zutylizowana przez GC zaraz po tym, jak zrobi to NoGateway.
2) Pośrednio tak, ale nie powinieneś się o to martwić. Twój kod jest poprawny w obecnej formie i nie możesz uniemożliwić użytkownikom łatwego zapomnienia o Utylizacji ().
źródło
Wzór z msdn
źródło
jest równa
Finalizator zostaje wezwany do zniszczenia twojego obiektu przez GC. Może to nastąpić w zupełnie innym momencie niż po odejściu z metody. Wyrzucenie IDisposable jest wywoływane natychmiast po opuszczeniu używanego bloku. Stąd wzorzec polega na tym, by używać zwalniania zasobów natychmiast po ich niepotrzebnym użyciu.
źródło
Z tego co wiem, zdecydowanie NIE zaleca się używania finalizatora / niszczyciela:
Wynika to głównie z tego, że nie wiadomo, kiedy lub JEŻELI zostanie on wywołany. Metoda utylizacji jest znacznie lepsza, szczególnie jeśli korzystasz z nas bezpośrednio lub pozbywasz się.
używanie jest dobre. Użyj tego :)
źródło