Zastosowanie metody Finalize / Dispose w C #


381

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:

  1. 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?

  2. Jeśli jednak opracuję klasę, która nie korzysta z żadnego niezarządzanego zasobu - bezpośrednio lub pośrednio, czy powinienem zaimplementować IDisposableopcję, 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
    }
  3. 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:

  1. 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
  2. Korzystam z WebClientklasy w mojej NoGatewayklasie. Ponieważ WebClientimplementuje IDisposableinterfejs, czy oznacza to, że WebClientpoś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?


1
czy ten skomplikowany wzorzec projektowy jest rzeczywiście wymagany do rozwiązania tego problemu udostępniania zasobów?
zinking

Odpowiedzi:


422

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:

public sealed class A : IDisposable
{
    public void Dispose()
    {
        // get rid of managed resources, call Dispose on member variables...
    }
}

Wdrażając niezamkniętą klasę, wykonaj następujące czynności:

public class B : IDisposable
{    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    }

    // only if you use unmanaged resources directly in B
    //~B()
    //{
    //    Dispose(false);
    //}
}

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śli SuppressFinalizejest wywoływany.

Nie powinieneś więc zadeklarować finalizatora, chyba że musisz, ale dajesz spadkobiercom swojej klasy haczyk, aby zadzwonić do ciebie Disposei sami zaimplementować finalizator, jeśli bezpośrednio używają niezarządzanych zasobów:

public class C : B
{
    private IntPtr m_Handle;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }
        ReleaseHandle(m_Handle);

        base.Dispose(disposing);
    }

    ~C() {
        Dispose(false);
    }
}

Jeśli nie używasz bezpośrednio niezarządzanych zasobów ( SafeHandlea 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 że Bnie ma finalizatora, nadal wywołuje SuppressFinalizeprawidł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ę w using(...) {}, aby upewnić się, że wszelkie niezarządzane zasoby zostaną usunięte w razie potrzeby.


26
Zgadzam się z thecoop. Pamiętaj, że nie potrzebujesz finalizatora, jeśli masz do czynienia tylko z zarządzanymi zasobami (w rzeczywistości NIE powinieneś próbować uzyskiwać dostępu do zarządzanych obiektów z poziomu finalizatora (innego niż „ten”), ponieważ nie ma gwarantowanej kolejności, w której GC wyczyści obiekty. Ponadto, jeśli używasz .Net 2.0 lub lepszej wersji, możesz (i powinieneś) używać SafeHandles do owijania niezarządzanych uchwytów. Safehandles znacznie zmniejszają twoją potrzebę pisania finalizatorów dla zarządzanych klas w ogóle. Blogs.msdn. com / bclteam / archive / 2005/03/16 / 396900.aspx
JMarsch

5
Myślę, że lepiej jest wywołać wywołanie MessageBox.Show („Błąd” + GetType (). Nazwa + „nie wyrzucone”) w finalizatorze, ponieważ obiekt jednorazowy powinien ZAWSZE zostać usunięty, a jeśli tego nie zrobisz, to najlepiej jak najszybciej powiadomić o tym fakcie.
erikkallen,

95
@erikkallen to żart? :)
Alex Norcliffe

2
potrzebny jest dodatkowy wysiłek obliczeniowy w CLR, aby śledzić klasy z aktywnymi finalizatorami. - Wdrożenie finalizatora powoduje, że tak się dzieje. Wywołanie GC.SuppressFinalize oznacza, że ​​finalizator nie powinien być wywoływany przez środowisko wykonawcze. Niezależnie od tego nadal jest w Gen2. Nie dodawaj finalizatora, jeśli nie masz do czynienia z zarządzanymi zasobami. Modyfikowane klasy zapieczętowane lub niezapieczętowane nie mają w tym przypadku znaczenia.
Ritch Melton,

3
@Ritch: cytowanie? To niekoniecznie zła rzecz; jeśli wdrażasz IDisposable, są szanse, że i tak pozostanie na jakiś czas. Zapisujesz CLR, starając się skopiować go z Gen0 -> Gen1 -> Gen2
thecoop

123

IDisposableTrudno zrozumieć oficjalny wzorzec wdrożenia . Uważam, że ten jest lepszy :

public class BetterDisposableClass : IDisposable {

  public void Dispose() {
    CleanUpManagedResources();
    CleanUpNativeResources();
    GC.SuppressFinalize(this);
  }

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Nawet lepsze rozwiązanie ma mieć regułę, którą zawsze trzeba utworzyć klasy otoki dla każdego zasobu niekontrolowana, że trzeba uchwycie:

public class NativeDisposable : IDisposable {

  public void Dispose() {
    CleanUpNativeResource();
    GC.SuppressFinalize(this);
  }

  protected virtual void CleanUpNativeResource() {
    // ...
  }

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

W przypadku SafeHandletych 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:

public class ManagedDisposable : IDisposable {

  public virtual void Dispose() {
    // dispose of managed resources
  }

}

@Kyle: Dzięki! I jak to zbyt :-) Jest kontynuacją nim tutaj .
Jordão,

4
Chociaż jedną rzeczą, na którą chcę zwrócić uwagę, jest to, że nie przeszkadza to, że zostaniemy wezwani drugi raz.
HuseyinUslu

5
@HuseyinUslu: to tylko esencja tego wzoru. Z pewnością możesz dodać disposedflagę i odpowiednio sprawdzić.
Jordão

2
@didibus: wystarczy dodać disposedflagę, 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?
Jordão

1
+1 za „Jeszcze lepszym rozwiązaniem jest posiadanie reguły, że zawsze musisz utworzyć klasę opakowania dla każdego niezarządzanego zasobu, z którym musisz sobie poradzić” . Natknąłem się na to w dodatku do VLC i od tego czasu go używam. Oszczędza tylu bólów głowy ...
Franz B.

37

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:

#region IDisposable implementation

//TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable

// Default initialization for a bool is 'false'
private bool IsDisposed { get; set; }

/// <summary>
/// Implementation of Dispose according to .NET Framework Design Guidelines.
/// </summary>
/// <remarks>Do not make this method virtual.
/// A derived class should not be able to override this method.
/// </remarks>
public void Dispose()
{
    Dispose( true );

    // This object will be cleaned up by the Dispose method.
    // Therefore, you should call GC.SupressFinalize to
    // take this object off the finalization queue 
    // and prevent finalization code for this object
    // from executing a second time.

    // Always use SuppressFinalize() in case a subclass
    // of this type implements a finalizer.
    GC.SuppressFinalize( this );
}

/// <summary>
/// Overloaded Implementation of Dispose.
/// </summary>
/// <param name="isDisposing"></param>
/// <remarks>
/// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios.
/// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.</item>
/// <item>If <paramref name="isDisposing"/> equals false, the method has been called by the 
/// runtime from inside the finalizer and you should not reference 
/// other objects. Only unmanaged resources can be disposed.</item></list></para>
/// </remarks>
protected virtual void Dispose( bool isDisposing )
{
    // TODO If you need thread safety, use a lock around these 
    // operations, as well as in your methods that use the resource.
    try
    {
        if( !this.IsDisposed )
        {
            if( isDisposing )
            {
                // TODO Release all managed resources here

                $end$
            }

            // TODO Release all unmanaged resources here



            // TODO explicitly set root references to null to expressly tell the GarbageCollector
            // that the resources have been disposed of and its ok to release the memory allocated for them.


        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );

        this.IsDisposed = true;
    }
}

//TODO Uncomment this code if this class will contain members which are UNmanaged
// 
///// <summary>Finalizer for $className$</summary>
///// <remarks>This finalizer will run only if the Dispose method does not get called.
///// It gives your base class the opportunity to finalize.
///// DO NOT provide finalizers in types derived from this class.
///// All code executed within a Finalizer MUST be thread-safe!</remarks>
//  ~$className$()
//  {
//     Dispose( false );
//  }
#endregion IDisposable implementation

Oto kod implementujący IDisposable w klasie pochodnej. Zauważ, że nie musisz jawnie wymieniać dziedziczenia po IDisposable w definicji klasy pochodnej.

public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass)


protected override void Dispose( bool isDisposing )
{
    try
    {
        if ( !this.IsDisposed )
        {
            if ( isDisposing )
            {
                // Release all managed resources here

            }
        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );
    }
}

Opublikowałem tę implementację na swoim blogu pod adresem: Jak prawidłowo wdrożyć wzorzec usuwania


Czy ktoś może również dodać wzór dla klasy pochodnej (pochodzącej z tej klasy podstawowej)
akjoshi

3
@akjoshi - Zaktualizowałem powyższy wzorzec, aby zawierał kod pochodnej klasy jednorazowej. Zauważ też, NIGDY nie implementuj finalizatora w klasie pochodnej ...
Dave Black

3
Wydaje się, że Microsoft lubi ustawiać flagę „disposed” na końcu metody disposed, ale wydaje mi się to niewłaściwe. Nadmiarowe wezwania do „Dispose” nie powinny nic robić; podczas gdy normalnie nie można oczekiwać, że funkcja Dispose zostanie wywołana rekurencyjnie, takie rzeczy mogą się zdarzyć, jeśli ktoś spróbuje usunąć obiekt, który został pozostawiony w nieprawidłowym stanie na podstawie wyjątku, który wystąpił podczas budowy lub innej operacji. Myślę, że użycie flagi Interlocked.Exchangena liczbie całkowitej IsDisposedw nie-wirtualnej funkcji opakowania byłoby bezpieczniejsze.
supercat

@DaveBlack: Co jeśli twoja klasa podstawowa nie używa niezarządzanych zasobów, ale twoja klasa pochodna tak? Czy musisz zaimplementować Finalizer w klasie pochodnej? A jeśli tak, to skąd wiesz, że klasa podstawowa jeszcze go nie zaimplementowała, jeśli nie masz dostępu do źródła?
Didier A.

@DaveBlack „Opracowałem ten wzór w oparciu o informacje od kilku doskonałych„ bogów .NET ”. Jeśli jednym z bogów był Jon Skeet, posłucham twojej rady.
Elisabeth

23

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:

  1. 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.

  2. 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 ().

  3. 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.

  4. 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ć !.

  5. 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


3
Sądzę, że jest piąty przypadek: jeśli twoja klasa subskrybuje zdarzenia przy użyciu silnych referencji, powinieneś zaimplementować IDisposable i wyrejestrować się ze zdarzeń w metodzie Dispose.
Didier A.

Cześć Didibus. Tak, masz rację. Zapomniałem o tym. Zmodyfikowałem swoją odpowiedź, aby uwzględnić ją jako przypadek. Dzięki.
Dave Black

Dokumentacja MSDN dla wzorca unieszkodliwiania dodaje kolejny przypadek: „ROZWAŻYWANIE wdrożenia podstawowego wzorca unieszkodliwiania na klasach, które same nie posiadają niezarządzanych zasobów lub obiektów jednorazowych, ale prawdopodobnie mają takie podtypy. Świetnym przykładem tego jest System.IO .Stream klasa. Chociaż jest to abstrakcyjna klasa podstawowa, która nie posiada żadnych zasobów, większość jej podklas ma i dlatego implementuje ten wzorzec. "
Gonen I,

12

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:

  • wiedzą, że muszą używać IDisposable
  • pamiętaj, aby używać „za pomocą”.

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ł

using (Var conn = Factory.MakeConnection())
{
     conn.Query(....);
}

Nowy sposób

Factory.DoWithConnection((conn)=>
{
    conn.Query(...);
}

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

void DoWithConnection(Action<SqlConnection> action)
{
   using (var conn = MakeConnection())
   {
       action(conn);
   }
}

MakeConnection jest teraz prywatny


2
Owijanie rzeczy w lambdas może być dobrym podejściem, ale ma ograniczenia. Nie jest tak źle w sytuacjach, w których w rzeczywistości wszyscy konsumenci klasy zastosowaliby blok „używający”, ale uniemożliwiałby sytuacje, w których metoda przechowywałaby IDisposable w polu klasy (bezpośrednio lub w iteratorze ).
supercat

@ supercat możesz argumentować, że niedozwolone jest przechowywanie zasobów gromadzących zasoby, co jest dobrą rzeczą. Model pożyczkowy, który tu proponuję, zmusza cię do
odchudzania

Może to być dobra rzecz, ale może również bardzo utrudnić niektóre bardzo rozsądne operacje. Załóżmy na przykład, że typ czytnika bazy danych zamiast implementować IEnumerable <T>, udostępnia metodę 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ą zaimplementowane IEnumerable<T>, można wykonać operację scalania, ale to nie zadziała DoForAll.
supercat,

Jedynym sposobem, w jaki mogę wymyślić połączenie dwóch DoForAllkolekcji 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ć.
supercat,

-1: dobra odpowiedź na pytanie, które nie zostało zadane. To byłaby świetna odpowiedź na pytanie „w jaki sposób ułatwić korzystanie z przedmiotów IDisposable”
John Saunders,

10

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

  • ich obviuos ich z doświadczenia (na przykład klasa gniazd)
  • jest udokumentowane
  • są ostrożni i widzą, że klasa implementuje IDisposable

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


1
Nie rozumiem, dlaczego deweloper nie używałby używania / usuwania na obiekcie implementującym IDisposable (chyba że program i tak ma zamiar wyjść).
adrianm

1
Chodzi o to, że deweloper musiałby napisać wszystkie wywołania, aby usunąć je we wszystkich ścieżkach kodu, które skutkują odsyłaniem do niego. SO na przykład, jeśli wstawię instancję do słownika, kiedy usuwam wpisy ze słownika, muszę wywołać dispose. W tym przypadku jest wiele problemów, które nie są potrzebne - obiekt nie musi być usunięty
pm100

3
@ pm100 Re: Niepotrzebne wdrażanie IDisposable - na stronie codeproject.com/KB/dotnet/idisposable.aspx znajduje się szczegółowy artykuł, który omawia rzadkie przypadki, w których możesz chcieć o tym pomyśleć (bardzo rzadko, jestem tego pewien). Krótko mówiąc: jeśli możesz przewidzieć potrzebę użycia IDisposable w przyszłości lub obiektu pochodnego, możesz pomyśleć o implementacji IDisposable jako „braku operacji” w swojej klasie podstawowej, aby uniknąć problemów związanych z „krojeniem”, gdzie niektóre obiekty pochodne wymagają utylizacja, a inne nie.
Kevin P. Rice

4
  1. 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.

  2. 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:

    • Wiedz, że wkrótce będziesz mieć ograniczone zasoby w swoich obiektach, tylko nie teraz (i mam na myśli, że ponieważ w „wciąż się rozwijamy, będzie tu, zanim skończymy”, a nie w „Myślę, że będziemy tego potrzebować „)
    • Korzystanie z ograniczonych zasobów
  3. 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ć usingtak, jak pokazano.

  4. (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.


4

Usuń wzór:

public abstract class DisposableObject : IDisposable
{
    public bool Disposed { get; private set;}      

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~DisposableObject()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (!Disposed)
        {
            if (disposing)
            {
                DisposeManagedResources();
            }

            DisposeUnmanagedResources();
            Disposed = true;
        }
    }

    protected virtual void DisposeManagedResources() { }
    protected virtual void DisposeUnmanagedResources() { }
}

Przykład dziedziczenia:

public class A : DisposableObject
{
    public Component components_a { get; set; }
    private IntPtr handle_a;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeManagedResources");
          components_a.Dispose();
          components_a = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeUnmanagedResources");
          CloseHandle(handle_a);
          handle_a = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

public class B : A
{
    public Component components_b { get; set; }
    private IntPtr handle_b;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeManagedResources");
          components_b.Dispose();
          components_b = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeUnmanagedResources");
          CloseHandle(handle_b);
          handle_b = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

4

Niektóre aspekty innej odpowiedzi są nieco niepoprawne z 2 powodów:

Pierwszy,

using(NoGateway objNoGateway = new NoGateway())

faktycznie odpowiada:

try
{
    NoGateway = new NoGateway();
}
finally
{
    if(NoGateway != null)
    {
        NoGateway.Dispose();
    }
}

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”)

using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
    if (NoGateway != null)
    {
        ...

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:

Finalizator zostaje wezwany do zniszczenia twojego obiektu przez GC

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


2
Czy jesteś pewien pisania NoGateway = new NoGateway();i NoGateway != null?
Cœur

1
Czy odnosiło się to do stackoverflow.com/a/898856/3195477 ? Obecnie nie ma odpowiedzi pod nazwą „Icey”
UuDdLrLrSs

@DaveInCaz wygląda na to, że to prawda. Nigdzie nie widzę „Icey”, ale kontekst mojej odpowiedzi wydaje się być ukierunkowany na odpowiedź podaną przez powyższy link. Może zmienił nazwę użytkownika?
Dave Black

@DaveBlack cool, dzięki. Właśnie edytowałem to w tekście.
UuDdLrLrSs

2

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 ().


2

Wzór z msdn

public class BaseResource: IDisposable
{
   private IntPtr handle;
   private Component Components;
   private bool disposed = false;
   public BaseResource()
   {
   }
   public void Dispose()
   {
      Dispose(true);      
      GC.SuppressFinalize(this);
   }
   protected virtual void Dispose(bool disposing)
   {
      if(!this.disposed)
      {        
         if(disposing)
         {
            Components.Dispose();
         }         
         CloseHandle(handle);
         handle = IntPtr.Zero;
       }
      disposed = true;         
   }
   ~BaseResource()      
   {      Dispose(false);
   }
   public void DoSomething()
   {
      if(this.disposed)
      {
         throw new ObjectDisposedException();
      }
   }
}
public class MyResourceWrapper: BaseResource
{
   private ManagedResource addedManaged;
   private NativeResource addedNative;
   private bool disposed = false;
   public MyResourceWrapper()
   {
   }
   protected override void Dispose(bool disposing)
   {
      if(!this.disposed)
      {
         try
         {
            if(disposing)
            {             
               addedManaged.Dispose();         
            }
            CloseHandle(addedNative);
            this.disposed = true;
         }
         finally
         {
            base.Dispose(disposing);
         }
      }
   }
}

1
using(NoGateway objNoGateway = new NoGateway())

jest równa

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

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.


1
Finalizator nie jest wzywany do zniszczenia obiektu przez GC. Jeśli „Finalizacja” zostanie zastąpiona, wówczas GC zniszczy obiekt , zostanie umieszczony w kolejce obiektów wymagających finalizacji, tymczasowo tworząc silne odniesienie do niego i - przynajmniej tymczasowo - „wskrzeszając” go.
supercat

-5

Z tego co wiem, zdecydowanie NIE zaleca się używania finalizatora / niszczyciela:

public ~MyClass() {
  //dont use this
}

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 :)


2
Powinieneś podążać za linkiem w odpowiedzi thecoop. Tak, użycie / Dispose jest lepsze, ale klasa Jednorazowa powinna zdecydowanie zaimplementować oba.
Henk Holterman

Co ciekawe, wszystkie dokumenty, które przeczytałem z Microsoft - np. Wytyczne dotyczące projektowania ram - mówią, że NIGDY nie używaj destruktora. Zawsze używaj IDisposable.
Nic Wise

5
Po prostu rozróżnij używanie klasy od pisania klasy, przeczytaj je ponownie.
Henk Holterman

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.