Czy wdrażam IDisposable poprawnie?

Ta klasa używa {[1] } i dlatego implementuje IDisposable.

public class Foo : IDisposable
{
    private StreamWriter _Writer;

    public Foo (String path)
    {
        // here happens something along the lines of:
        FileStream fileWrite = File.Open (path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
        _Writer = new StreamWriter (fileWrite, new ASCIIEncoding ());
    }

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

    ~Foo()
    {
        Dispose (false);
    }

    protected virtual void Dispose (bool disposing)
    {
        if (_Disposed) {
            return;
        }
        if (disposing) {
            _Writer.Dispose ();
        }
        _Writer = null;
        _Disposed = true;
    }
    private bool _Disposed;
}

}

Czy jest jakiś problem z obecną implementacją? Czyli, czy muszę ręcznie wypuścić podstawowy FileStream? Czy Dispose(bool) jest napisane poprawnie?

Author: mafu, 2009-07-16

7 answers

Nie musisz używać tej rozbudowanej wersji implementacji IDisposable, jeśli twoja klasa nie używa bezpośrednio niezarządzanych zasobów.

A simple

 public virtual void Dispose()
 {

     _Writer.Dispose();
 }
Wystarczy.

Jeśli twój konsument nie pozbędzie się Twojego obiektu, będzie on normalnie GC 'D bez wezwania do usunięcia, obiekt przechowywany przez _Writer będzie również GC' D i będzie miał finalizer, więc nadal będzie mógł prawidłowo wyczyścić swoje niezarządzane zasoby.

Edit

Po zrobieniu kilku badania nad linkami podanymi przez Matta i innych doszedłem do wniosku, że moja odpowiedź tutaj stoi. Oto dlaczego: -

Założenie stojące za implementacją disposable "pattern" (mam przez to na myśli protected virtual Dispose(bool), SuppressFinalize itp. marlarky) na dziedziczonej klasie jest to, że podklasa Może trzymać się niezarządzanego zasobu.

[1]} jednak w realnym świecie zdecydowana większość z nas programistów. NET nigdy nie zbliży się do niezarządzanego zasoby. Jeśli miałbyś kwantyfikować "Może" powyżej jakiej liczby probabilistycznej byś wymyślił dla siebie kodowanie. NET?

Załóżmy, że mam Typ Person (który ze względu na argument ma typ jednorazowego użytku w jednym ze swoich pól i dlatego powinien być jednorazowy). Teraz mam spadkobierców klienta, pracownika itp. Czy naprawdę rozsądne jest dla mnie zaśmiecanie klasy osób tym "wzorem" na wypadek, gdyby ktoś dziedziczył osobę i chciał trzymać niezarządzaną zasoby?

Czasami my programiści możemy zbytnio skomplikować sprawy próbując kodować wszystkie możliwe okoliczności bez używania zdrowego rozsądku odnośnie względnego prawdopodobieństwa takich okoliczności.

Gdybyśmy kiedykolwiek chcieli użyć niezarządzanego zasobu bezpośrednio, sensowny wzorzec byłby zawijany w jego własną klasę, gdzie pełny" jednorazowy wzorzec " byłby rozsądny. Stąd w znacznie dużej części "normalnego" kodu nie musimy się martwić o wszystkie to Pieprzenie. Jeśli potrzebujemy IDisposable możemy użyć prostego wzoru powyżej, dziedzicznego lub nie.

Cieszę się, że mam to z głowy. ;)
 39
Author: AnthonyWJones,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-17 19:23:10

Nie potrzebujesz metody Finalize (Destruktor), ponieważ nie masz żadnych niezarządzanych obiektów. Należy jednak zachować wywołanie GC.SuppressFinalize w przypadku, gdy Klasa dziedzicząca z Foo ma niezarządzane obiekty, a tym samym finalizer.

Jeśli uczynisz klasę zapieczętowaną, to wiesz, że niezarządzane obiekty nigdy nie wejdą do równania, więc nie jest konieczne dodawanie przeciążenia protected virtual Dispose(bool) lub GC.SuppressFinalize.

Edit:

@AnthonyWJones sprzeciw wobec tego jest taki, że jeśli wiesz, że podklasy nie będą odwoływać się do niezarządzanych obiektów, całość Dispose(bool) i GC.SuppressFinalize jest niepotrzebna. Ale jeśli tak jest, naprawdę powinieneś tworzyć klasy internal zamiast public, a metoda Dispose() powinna być virtual. Jeśli wiesz, co robisz, nie postępuj zgodnie z sugerowanym wzorem Microsoftu, ale powinieneś znać i rozumieć zasady, zanim je złamiesz!

 16
Author: Matt Howells,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-17 12:33:48

Zalecaną praktyką jest używanie finalizera tylko wtedy, gdy masz niezarządzane zasoby (takie jak natywne uchwyty plików, wskaźniki pamięci itp.).

Mam jednak dwie małe sugestie,

Nie trzeba mieć "m_Disposed " zmienna do sprawdzenia, czy wcześniej dzwoniłeś Dispose na zarządzanych przez Ciebie zasobach. Mógłbyś użyj podobnego kodu,

protected virtual void Dispose (bool disposing)
{
    if (disposing) {
        if (_Writer != null)
            _Writer.Dispose ();
    }
    _Writer = null;
}

Otwieranie plików tylko tak długo, jak to konieczne. Więc w twoim przykładzie przetestujesz na istnienie pliku w konstruktorze za pomocą File.Exists, a następnie gdy trzeba go odczytać/zapisać, wtedy można go otworzyć i użyć.

Również, jeśli tylko chcemy zapisać tekst do pliku, spójrz na File.WriteAllText lub File.OpenText lub nawet File.AppendText, który jest skierowany do plików tekstowych specjalnie z ASCIIEncoding.

Poza tym, tak, poprawnie implementujesz wzorzec. NET Dispose.

 4
Author: Mike J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-16 08:51:18

Mam wiele takich klas - i zalecam, aby klasa była zapieczętowana, gdy tylko jest to możliwe. IDisposable + dziedziczenie może działać, ale w 99% przypadków nie jest potrzebne - i łatwo popełnić błędy.

O ile nie piszesz publicznego API (w takim przypadku dobrze jest zezwolić ludziom na implementację Twojego kodu tak, jak chcą - np. używać IDisposable), nie musisz go wspierać.

Po prostu zrób:

public sealed class Foo : IDisposable
{
    private StreamWriter _Writer;

    public Foo(string path)
    {
            FileStream fileWrite = File.Open (path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
            try { 
                _Writer = new StreamWriter (fileWrite, new ASCIIEncoding());
            } catch {
                fileWrite.Dispose();
                throw;
            }
    }

    public void Dispose()
    {
         _Writer.Dispose();
    }
}

Zwróć uwagę na próbę...catch; technicznie konstruktor StreamWriter może rzucić wyjątek, w którym to przypadku nigdy nie przejmuje własności FileStream i musisz go usunąć samodzielnie.

Jeśli naprawdę używasz wielu IDisposables, rozważ umieszczenie tego kodu w C++ / CLI: to sprawia, że cały kod boilerplate jest dla Ciebie (transparentnie używa odpowiedniej deterministycznej technologii niszczenia zarówno dla obiektów natywnych, jak i zarządzanych).

[[8]}Wikipedia ma przyzwoitą próbkę IDisposable dla C++ (naprawdę, jeśli masz wiele IDisposable ' S, C++ jest rzeczywiście dużo prostsze niż C#): Wikipedia: Finalizery C++/CLI i zmienne automatyczne.

Na przykład implementacja" bezpiecznego " jednorazowego kontenera w C++/CLI wygląda tak...

public ref class MyDisposableContainer
{
    auto_handle<IDisposable> kidObj;
    auto_handle<IDisposable> kidObj2;
public:

    MyDisposableContainer(IDisposable^ a,IDisposable^ b) 
            : kidObj(a), kidObj2(b)
    {
        Console::WriteLine("look ma, no destructor!");
    }
};

Ten kod będzie poprawnie usuwał kidObj i kidObj2, nawet bez dodawania niestandardowej implementacji IDisposable, i jest solidny dla wyjątków w implementacji Disposable (nie, że te powinny wystąpić, ale nadal), i prosty w utrzymaniu w obliczu wielu innych IDisposable członków lub natywnych zasobów.

Nie to Jestem wielkim fanem C++ / CLI, ale jak na mocno zorientowany na zasoby kod ma c# beat łatwo, i ma absolutnie genialny interop zarówno z kodem zarządzanym, jak i natywnym - krótko mówiąc, doskonały kod kleju; -). Zazwyczaj piszę 90% mojego kodu w C#, ale używam C++ / CLI dla wszystkich potrzeb interop (esp. jeśli chcesz wywołać jakąkolwiek funkcję win32 - MarshalAs i inne atrybuty interop są przerażające i całkowicie niezrozumiałe).

 3
Author: Eamon Nerbonne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-20 13:23:42

Należy sprawdzić, czy _Writer nie jest null przed próbą pozbycia się go. (Nie wydaje się prawdopodobne, że kiedykolwiek będzie null, ale na wszelki wypadek!)

protected virtual void Dispose(bool disposing)
{
    if (!_Disposed)
    {
        if (disposing && (_Writer != null))
        {
            _Writer.Dispose();
        }
        _Writer = null;
        _Disposed = true;
    }
}
 1
Author: LukeH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-16 10:05:21

Jeśli otworzysz StreamWriter, musisz go również wyrzucić, albo będziesz miał przeciek.

 0
Author: Tal Pressman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-16 08:36:25

Zgadzam się ze wszystkim, co zostało powiedziane w innych komentarzach, ale chciałbym też zaznaczyć, że;

  1. Nie musisz ustawiać _Writer = null w obu przypadkach.

  2. Jeśli masz zamiar to zrobić, prawdopodobnie najlepiej umieścić go w if, gdzie znajduje się dispose. Prawdopodobnie nie robi to dużej różnicy, ale generalnie nie powinieneś bawić się zarządzanymi obiektami, gdy jesteś usuwany przez finalizer (czego i tak nie potrzebujesz w tym przypadku, jak wskazali inni out).

 0
Author: Yort,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-16 15:10:59