2013-09-23 23 views
10

Istnieje wiele informacji wokół o „standardowej pełnej” IDisposable wdrożenia do usuwania niezarządzalny zasobów - ale w rzeczywistości ta sprawa jest (bardzo) rzadkie (większość zasoby są już owinięty przez zarządzanych klas). Pytanie to koncentruje się na mimimalnej implementacji IDisposed dla znacznie bardziej powszechnego przypadku "tylko zarządzanych zasobów".Minimal IDispose implimenation zarządzanych zasobów tylko

1: Czy mimimal wdrożenie IDisposable w kodzie poniżej poprawny, czy są jakieś problemy?

2: Czy istnieje jakikolwiek powód, aby dodać pełną standardową implualizację IDisposible (Dispose(), Dispose (bool), Finalizer itp.) W stosunku do przedstawionej minimalnej implementacji?

3: Czy to jest OK/mądry w tym minimalnym przypadku uczynienia utylizować wirtualny (ponieważ nie zapewniając Dispose (bool))?

4: Jeśli ta minimalna realizacja jest zastąpić pełnej standardowej implementacji, który zawiera (bezużyteczne, w tym przypadku) finalizatora - Czy ta zmiana jak GC obsługuje obiekt? Czy jest jakiś minus?

5: Przykład zawiera programatory czasowe i zdarzenia, ponieważ te przypadki są szczególnie ważne, aby ich nie pominąć, ponieważ nieudane ich usunięcie utrzyma obiekty przy życiu i kopie ("to" w przypadku Timera, źródła zdarzeń w przypadku obsługi zdarzeń) dopóki GC nie zostanie ukarany, aby pozbyć się ich w swoim czasie. Czy są jakieś inne podobne przykłady?

class A : IDisposable { 
    private Timer timer; 
    pubic A(MyEventSource eventSource) { 
     eventSource += Handler 
    } 

    private void Handler(object source, EventArgs args) { ... } 

    public virtual void Dispose() { 
     timer.Dispose(); 
     if (eventSource != null) 
      eventSource -= Handler; 
    } 
} 

class B : A, IDisposable { 
    private TcpClient tpcClient; 

    public override void Dispose() { 
     (tcpClient as IDispose).Dispose(); 
     base.Dispose(); 
    } 
} 

bibl:
MSDN
SO: When do I need to manage managed resources
SO: How to dispose managed resource in Dispose() method in C#
SO: Dispose() for cleaning up managed resources

+1

To jest 1) i 1). Wygląda na to, że już to wiesz. –

+0

@HansPassant Przeformułowałem pytanie. Twój wkład byłby doceniony. – Ricibob

Odpowiedz

7
  1. Realizacja jest prawidłowa, nie ma problemów, o ile nie pochodzą bezpośrednio klasa posiada zasób niezarządzanej.

  2. Jeden dobry powód, aby realizować pełny wzór jest „zasada najmniejszego zaskoczenia”. Ponieważ w MSDN nie ma autorytatywnego dokumentu opisującego ten prostszy wzór, deweloperzy zajmujący się konserwacją w dalszym ciągu mogą mieć wątpliwości - nawet Ty poczułeś potrzebę zwrócenia się do StackOverflow :)

  3. Tak, jest w porządku, aby Dispose był wirtualny w tym przypadku.

  4. narzut niepotrzebnego finalizatora jest znikoma jeśli utylizować zostało nazwane, i jest właściwie wdrożony (tj wzywa GC.SuppressFinalize)

Zdecydowana większość IDisposable zajęć poza Framework Sam są IDisposable, ponieważ są właścicielami zarządzanych zasobów IDisposable. To rzadkie dla nich bezpośrednio posiadać zasób niezarządzanej - to się dzieje tylko przy użyciu P/Invoke dostęp do zasobów niezarządzanego, które nie są narażone przez .NET Framework.

Dlatego nie jest dobrym argumentem za propagowanie tego prostszy wzór:

  • W rzadkich przypadkach niezarządzani zasoby są wykorzystywane, powinny być one zapakowane w szczelnym IDisposable klasy otoki, który implementuje finalizatora (jak SafeHandle).Ponieważ jest on zapieczętowany, ta klasa nie potrzebuje pełnego IDisposable pattern.

  • We wszystkich innych przypadkach zdecydowana większość może posłużyć się prostszym wzorem.

Ale dopóki Microsoft lub inne wiarygodne źródło aktywnie promuje go, będę nadal korzystać z pełnej IDisposable wzór.

+0

To. Jestem też fanem używania pełnego schematu, ale widzę punkt PO. W każdym razie +1. –

+0

Doskonała odpowiedź - dziękuję. Wydaje się, że istnieje nieproporcjonalna dokumentacja dotycząca pełnego wdrożenia IDispose dla zasobów zarządzanych przez Unmanaged, gdy, jak stwierdzono, jest to bardzo rzadkie - i dobry punkt o zamkniętym opakowaniu dla tego przypadku. Chciałem tylko sprawdzić, czy moja wiedza na temat tego, co ogranicza pełne wdrożenie w przypadku zasobów zarządzanych, była poprawna. Dzięki. – Ricibob

+0

@Ricibob, Chciałabym, aby Microsoft dostarczył za pośrednictwem MSDN kilka bardzo wyraźnych wskazówek, w tym, co robić, gdy masz do dyspozycji wiele zasobów i jedną z rzutów metodami Dispose. Czy powinieneś używać bloków try/catch, aby spróbować usunąć jak najwięcej swoich posiadanych zasobów? IMHO prawdopodobnie powinieneś, ale istnieje przykład w Framework (System.ComponentModel.Container), który tego nie zrobi. – Joe

0

Moja zaleca Dispose wzór jest dla nie-wirtualnej Dispose realizacji do łańcucha do wirtualnego void Dispose(bool), najlepiej po czymś takim:

int _disposed; 
public bool Disposed { return _disposed != 0; } 
void Dispose() 
{ 
    if (System.Threading.Interlocked.Exchange(ref _disposed, 1) != 0) 
    Dispose(true); 
    GC.SuppressFinalize(this); // In case our object holds references to *managed* resources 
} 

Stosując to podejście zapewni Dispose(bool) tylko jest wywoływana raz, nawet jeśli wielokrotność wątki próbują wywoływać je jednocześnie. Chociaż takie równoczesne próby unieszkodliwiania są rzadkie (*), tanie jest strzeżenie przed nimi; jeśli klasa podstawowa nie robi czegoś podobnego do powyższego, każda klasa pochodna musi mieć swoją własną nadmiarową logikę podwójnego dozoru i prawdopodobnie również flagę nadmiarową.

(*) Niektóre klasy komunikacyjne, które w większości są jednowątkowe i używają blokujących operacji we/wy, umożliwiają wywoływanie Dispose z dowolnego kontekstu wątków w celu anulowania operacji we/wy, która blokuje własny wątek [oczywiście Dispose nie może być wezwanym do tego wątku, ponieważ ten wątek nie może zrobić niczego, co jest zablokowane]. Całkowicie możliwe jest - i nie nierozsądne - aby takie obiekty lub obiekty, które je enkapsulują, by miały zewnętrzną nitkę, próbowałyby je anulować w momencie, w którym miały zostać usunięte przez ich główną sieć. wątek. Połączenia symultaniczne Dispose najprawdopodobniej będą rzadkie, ale ich możliwość nie oznacza żadnego "problemu projektowego", pod warunkiem, że kod Dispose może zadziałać na jedno połączenie i zignorować drugie.

+1

Osobiście uważam, że ten wzór jest przesadny w przypadku większości klas IDisposable, dla których wielowątkowe usuwanie jest prawie sprzeczne pod względem. Dodanie zablokowanego przyrostu jest tanie pod względem czasu realizacji, ale być może nie jest tanie pod względem czasu spędzanego przez programistę serwisowego, zastanawiającego się, dlaczego tak się dzieje. Nie przekonuję się również twojego hipotetycznego przykładu: IMHO ujawnia API, aby inne wątki przerywały operację blokowania I/O, nie wymaga wzoru Dispose - prawdopodobnie lepszy interfejs API, taki jak "InterruptBlockingIO" jest lepszy, a zablokowany wątek może zrobić Dispose – Joe

+0

A lotne bool jest Wysuwany prostsze? Również w przypadku ściśle zarządzanych zasobów (przypadki MOST!) Nie ma już potrzeby przeprowadzania już sprawdzonej kontroli - ponieważ wszystko, co robimy, wywołuje Dispose on members lub base - i te metody Dispose powinny już uwzględniać podwójne wywołanie. – Ricibob

+0

@Ricibob: Użycie opcji 'Interlocked.Exchange' spowoduje 100% ochronę przed podwójnym wywołaniem; lotny 'bool' nie będzie. Jeśli chodzi o konieczność takiej ochrony, podczas gdy ja zgodziłbym się, że zwykle można uciec bez niej, to jest tanie, i sugerowałbym, że generalnie łatwiej jest zapewnić taką ochronę, niż udowodnić, że nie jest potrzebna. Załóżmy na przykład, że 'Foo' posiada dwa zarządzane zasoby" podwójnie rozładowane "i należy je uporządkować. Wątek 1 wywołuje 'Foo.Dispose' i rozpoczyna usuwanie pierwszego obiektu, gdy wątek 2 wywołuje' Foo.Dispose'. – supercat

2

Inną opcją jest refaktoryzacja kodu, aby uniknąć dziedziczenia i uszczelnienie klas IDisposable. Prostszy wzór jest łatwy do uzasadnienia, ponieważ niezręczne zawirowania na rzecz ewentualnego dziedziczenia nie są już konieczne. Osobiście stosuję to podejście przez większość czasu; w rzadkim przypadku, gdy chcę zrobić niezamkniętą klasę jednorazową, po prostu podążam za wzorcem "standardowym". Jedną z fajnych rzeczy w kultywowaniu tego podejścia jest to, że zwykle popycha cię do kompozycji, a nie do dziedziczenia, co generalnie ułatwia kodowanie i testowanie kodu.

+0

Całkowicie się zgadzam. Im z tła C++ - gdzie dziedziczenie wydawało się preferowanym podejściem - ale stopniowo uczenie się kompostowania czyni kod prostszym. – Ricibob