2009-09-13 11 views
20

Nie sądzę, aby to pytanie było wcześniej zadawane. Jestem nieco zdezorientowany, jeśli chodzi o najlepszy sposób implementacji IDisposable na zamkniętej klasie, zamkniętej klasie, która nie dziedziczy po klasie bazowej. (Oznacza to "czystą zamkniętą klasę", która jest moim wymyślonym terminem.)Implementacja IDisposable w zamkniętej klasie

Być może niektórzy zgadzają się ze mną, że wytyczne dotyczące wdrażania IDisposable są bardzo mylące. Powiedział, że chcę wiedzieć, że sposób, w jaki zamierzam wdrożyć IDisposable jest wystarczający i bezpieczny.

Zrobiłem kod P/Invoke, który przydziela IntPtr do Marshal.AllocHGlobal i oczywiście, chcę czysto pozbyć się niezarządzanej pamięci, którą stworzyłem. Więc mam na myśli coś takiego

using System.Runtime.InteropServices; 

[StructLayout(LayoutKind.Sequential)] 
public sealed class MemBlock : IDisposable 
{ 
    IntPtr ptr; 
    int length; 

    MemBlock(int size) 
    { 
      ptr = Marshal.AllocHGlobal(size); 
      length = size; 
    } 

    public void Dispose() 
    { 
      if (ptr != IntPtr.Zero) 
      { 
       Marshal.FreeHGlobal(ptr); 
       ptr = IntPtr.Zero; 
       GC.SuppressFinalize(this); 
      } 
    } 

    ~MemBlock() 
    { 
      Dispose(); 
    }  
} 

jestem przy założeniu, że z powodu MemBlock jest całkowicie szczelny i nie wywodzi się z innej klasy, która realizuje virtual protected Dispose(bool disposing) nie jest konieczne.

Czy finalizator jest absolutnie niezbędny? Wszystkie myśli mile widziane.

Odpowiedz

13

Finalizator jest niezbędny jako mechanizm rezerwowy, aby ostatecznie uwolnić niezarządzane zasoby, jeśli zapomniałeś zadzwonić pod numer Dispose.

Nie, nie powinieneś zadeklarować metody virtual w klasie sealed. W ogóle się nie skompiluje. Ponadto nie zaleca się deklarowania nowych członków w klasach protected w klasach .

+0

Ale oczywiście w przypadku, gdy zamknięta klasa wywodzi się z klasy bazowej, wówczas konieczna byłaby wirtualna utylizacja - poprawna? – zebrabox

+0

Również. Finalizator oznacza dodanie do kolejki finalizatora i konieczność efektywnego podwójnego zbierania śmieci. Wydaje się, że trzeba zapłacić wysoką cenę za niewykorzystane zasoby. Czy nie ma sposobu, aby uniknąć uderzenia wydajności? – zebrabox

+0

W takim przypadku "przesłonisz" metodę. Nie możesz zadeklarować żadnych metod w klasie 'zapieczętowanej' jako' wirtualnej'. Jest to błąd ** kompilatora **. –

7

Dodatek dodatkowy; w przypadku ogólne, wspólny wzorzec ma mieć metodę Dispose(bool disposing), dzięki czemu wiesz, czy jesteś w Dispose (gdzie jest więcej dostępnych rzeczy) w stosunku do finalizatora (gdzie nie powinieneś naprawdę dotykać żadnych innych połączonych zarządzanych obiektów) .

Na przykład:

public void Dispose() { Dispose(true); } 
~MemBlock() { Dispose(false); } 
void Dispose(bool disposing) { // would be protected virtual if not sealed 
    if(disposing) { // only run this logic when Dispose is called 
     GC.SuppressFinalize(this); 
     // and anything else that touches managed objects 
    } 
    if (ptr != IntPtr.Zero) { 
      Marshal.FreeHGlobal(ptr); 
      ptr = IntPtr.Zero; 
    } 
} 
+0

Tak, dobry punkt Marc, ale gdybym wiedział, że tylko pozbywam się zasobów niezarządzanych, czy to jest absolutnie konieczne? – zebrabox

+0

Również - bardzo głupie pytanie, ale jeśli wzór Dispose polega na deterministycznym uwalnianiu niezarządzanych zasobów, to dlaczego miałbym zajmować się jednorazowymi zarządzanymi zasobami, kiedy powinny zostać oczyszczone przez GC? – zebrabox

+0

Jeśli jesteś deterministyczny, to chciałbyś posprzątać wszystko, czym się zajmujesz *; zwłaszcza jeśli są one same w sobie "identyfikowalne". Nie zrobisz tego * w finalizatorze, ponieważ mogą już zostać zebrane (i: to już nie jest twoja praca). I masz rację; w tym przypadku, poza 'SuppressFinalize' (co nie ma większego znaczenia), nic nie robimy, więc dobrze byłoby nie zawracać sobie głowy; właśnie dlatego podkreśliłem * ogólny * przypadek. –

7

Od Joe Duffy's Weblog:

Dla zamkniętych klas, to wzór musi nie być przestrzegane, co oznacza, powinieneś prostu zaimplementować Finalizer i Usuwać z prostych metod (tj. ~ T() (Finalizacja) i Dispose() w języku C#). Wybierając drugą trasę, Twój kod powinien nadal być zgodny z poniższymi wytycznymi dotyczącymi implementacji finalizacji i logiki unieszkodliwienia.

Tak, powinieneś być dobry.

Potrzebujesz finalizatora, o którym wspomniał Mehrdad. Jeśli chcesz tego uniknąć, możesz rzucić okiem na SafeHandle. Nie mam wystarczającego doświadczenia z P/Invoke, aby zasugerować prawidłowe użycie.

+0

Dzięki TrueWill! Spojrzałem na SafeHandle i według Erica Lipperta zespół BCL postrzegał go jako jedną z najważniejszych korzyści, jakie wprowadzili w "Whidbey" (na razie nie można go znaleźć). Niestety jest to klasa abstrakcyjna, więc musisz przetasować własną dla każdej sytuacji, która trochę jest do bani – zebrabox

+1

@zebrabox: Chociaż w niektórych sytuacjach może zajść konieczność samodzielnego wprowadzenia, dokumentacja stwierdza: "Zestaw wstępnie nagranych klas wywodzących się z SafeHandle jest dostarczany jako abstrakcyjne wyprowadzenia, a ten zestaw znajduje się w przestrzeni nazw Microsoft.Win32.SafeHandles. " – TrueWill

+1

@TrueWill. Tak, to prawda, ale tylko w takich rzeczach, jak uchwyty do plików, uchwyty do czekania, uchwyty do rur i mnóstwo rzeczy krypt. Wciąż lepsze niż nic! – zebrabox

1

Nie można zadeklarować metod wirtualnych w zamkniętej klasie. Zgłoszenie chronionych członków w zamkniętej klasie daje ostrzeżenie kompilatora. Więc zaimplementowałeś to poprawnie. Wywołanie GC.SuppressFinalize (this) z finalizatora nie jest konieczne z oczywistych powodów, ale nie może zaszkodzić.

Posiadanie finalizatora jest niezbędne przy zarządzaniu zasobami niezarządzanymi, ponieważ nie są one automatycznie zwalniane, musisz to zrobić w finalizatorze z wywołaniem automatycznie po zebraniu śmieci.

Powiązane problemy