Prawidłowe wdrażanie IDisposable

145

W moich zajęciach implementuję IDisposable w następujący sposób:

public class User : IDisposable
{
    public int id { get; protected set; }
    public string name { get; protected set; }
    public string pass { get; protected set; }

    public User(int UserID)
    {
        id = UserID;
    }
    public User(string Username, string Password)
    {
        name = Username;
        pass = Password;
    }

    // Other functions go here...

    public void Dispose()
    {
        // Clear all property values that maybe have been set
        // when the class was instantiated
        id = 0;
        name = String.Empty;
        pass = String.Empty;
    }
}

W VS2012 moja analiza kodu mówi, aby poprawnie zaimplementować IDisposable, ale nie jestem pewien, co zrobiłem źle.
Dokładny tekst jest następujący:

CA1063 Implement IDisposable poprawnie Podaj możliwą do zastąpienia implementację Dispose (bool) w polu „User” lub oznacz typ jako zapieczętowany. Wywołanie Dispose (false) powinno tylko wyczyścić zasoby natywne. Wywołanie Dispose (true) powinno oczyścić zarówno zasoby zarządzane, jak i natywne. stman User.cs 10

Dla porównania: CA1063: poprawnie zaimplementuj IDisposable

Przeczytałem tę stronę, ale obawiam się, że tak naprawdę nie rozumiem, co należy tutaj zrobić.

Jeśli ktoś może wyjaśnić w bardziej lamerski sposób, na czym polega problem i / lub jak należy wdrożyć IDisposable, to naprawdę pomoże!

Ortund
źródło
1
Czy to cały kod w środku Dispose?
Claudio Redi
42
Należy zaimplementować metodę Dispose (), aby wywołać metodę Dispose () na dowolnym elemencie członkowskim klasy. Żaden z tych członków nie ma takiego. Dlatego nie należy implementować IDisposable. Resetowanie wartości właściwości jest bezcelowe.
Hans Passant
13
Musisz to zaimplementować tylko IDispoablewtedy, gdy masz niezarządzane zasoby do usunięcia (obejmuje to niezarządzane zasoby, które są opakowane ( SqlConnection,FileStream itp.) Nie można i nie należy wdrożyć IDisposable, jeśli tylko udało zasobów, takich jak tutaj. To jest, IMO, poważny problem z analizą kodu. Jest bardzo dobry w sprawdzaniu głupich małych reguł, ale nie sprawdza się w sprawdzaniu błędów koncepcyjnych.
jason,
51
Bardzo mnie to denerwuje, że niektórzy woleliby głosować negatywnie i zamknąć to pytanie, niż próbować pomóc osobie, która wyraźnie źle zrozumiała koncepcję. Jaka szkoda.
Ortund,
2
Więc nie głosuj przeciw, nie głosuj za, zostaw post na zero i zamknij pytanie pomocnym wskaźnikiem.
tjmoore

Odpowiedzi:

113

To byłaby prawidłowa implementacja, chociaż nie widzę niczego, co musisz usunąć w opublikowanym kodzie. Musisz wdrożyć tylko IDisposablewtedy, gdy:

  1. Masz niezarządzane zasoby
  2. Trzymasz się odniesień do rzeczy, które same są jednorazowe.

Żadna część przesłanego kodu nie musi zostać usunięta.

public class User : IDisposable
{
    public int id { get; protected set; }
    public string name { get; protected set; }
    public string pass { get; protected set; }

    public User(int userID)
    {
        id = userID;
    }
    public User(string Username, string Password)
    {
        name = Username;
        pass = Password;
    }

    // Other functions go here...

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

    protected virtual void Dispose(bool disposing)
    {
        if (disposing) 
        {
            // free managed resources
        }
        // free native resources if there are any.
    }
}
Daniel Mann
źródło
2
Kiedy zacząłem pisać w C #, powiedziano mi, że najlepiej jest korzystać z niego, using(){ }kiedy tylko jest to możliwe, ale aby to zrobić, musisz zaimplementować IDisposable, więc ogólnie wolę mieć dostęp do klasy poprzez użycie, zwł. jeśli potrzebuję zajęć tylko w jednej lub dwóch funkcjach
Ortund
62
@Ortund You misunderstood. Najlepiej jest używać usingbloku, gdy klasa implementuje IDisposable . Jeśli nie potrzebujesz jednorazowej klasy, nie wdrażaj jej. To nie ma sensu.
Daniel Mann,
5
@DanielMann Semantyka usingbloku wydaje się być atrakcyjna poza samym IDisposableinterfejsem. Wyobrażam sobie, że było więcej niż kilka nadużyć IDisposablewyłącznie w celu określenia zakresu.
Thomas
1
Na marginesie, jeśli chcesz zwolnić jakiekolwiek niezarządzane zasoby, powinieneś dołączyć Finalizer wywołujący Dispose (false), co pozwoli GC wywołać Finalizer podczas wykonywania czyszczenia pamięci (w przypadku, gdy Dispose nie został jeszcze wywołany) i odpowiednio zwolnij niezarządzane zasoby.
mariozski
4
Bez finalizatora w Twojej implementacji wywołanie GC.SuppressFinalize(this);jest bezcelowe. Jak zauważył @mariozski, finalizator pomógłby zapewnić, że Disposezostanie w ogóle wywołany, jeśli klasa nie jest używana wewnątrz usingbloku.
Haymo Kutschbach
57

Przede wszystkim nie musisz „czyścić” stringi int-ów - zostaną one automatycznie załatwione przez garbage collectora. Jedyne, co należy wyczyścić, Disposeto niezarządzane zasoby lub zarządzane zasoby, które implementują IDisposable.

Zakładając jednak, że jest to tylko ćwiczenie edukacyjne, zalecanym sposobem implementacji IDisposablejest dodanie „bezpiecznika”, aby upewnić się, że żadne zasoby nie są usuwane dwukrotnie:

public void Dispose()
{
    Dispose(true);

    // Use SupressFinalize in case a subclass 
    // of this type implements a finalizer.
    GC.SuppressFinalize(this);   
}
protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing) 
        {
            // Clear all property values that maybe have been set
            // when the class was instantiated
            id = 0;
            name = String.Empty;
            pass = String.Empty;
        }

        // Indicate that the instance has been disposed.
        _disposed = true;   
    }
}
D Stanley
źródło
3
+1, posiadanie flagi zapewniającej, że kod czyszczący zostanie wykonany tylko raz, jest o wiele lepsze niż ustawienie właściwości na null lub cokolwiek innego (zwłaszcza, że ​​to koliduje z readonlysemantyką)
Thomas
+1 za użycie kodu użytkownika (nawet jeśli zostanie on automatycznie wyczyszczony), aby było jasne, co tam się dzieje. Poza tym za to, że nie jest słonym żeglarzem i wbijał mu młotkiem za mały błąd, ucząc się jak wielu innych tutaj.
Murphybro2
42

Poniższy przykład przedstawia ogólne najlepsze rozwiązania dotyczące implementacji IDisposableinterfejsu. Odniesienie

Pamiętaj, że potrzebujesz destruktora (finalizatora) tylko wtedy, gdy masz niezarządzane zasoby w swojej klasie. A jeśli dodasz destruktor, powinieneś pominąć Finalizację w Dispose , w przeciwnym razie spowoduje to, że twoje obiekty będą znajdować się w pamięci przez dwa cykle bezużyteczne (uwaga: przeczytaj, jak działa finalizacja ). Poniższy przykład omówi wszystko powyżej.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        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.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing 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. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}
CharithJ
źródło
14

IDisposableistnieje, aby zapewnić Ci środki do czyszczenia niezarządzanych zasobów, które nie będą czyszczone automatycznie przez moduł wyrzucania elementów bezużytecznych.

Wszystkie zasoby, które „czyścisz”, są zasobami zarządzanymi, a zatem Twoja Disposemetoda nic nie daje. Twoja klasa nie powinna w ogóle implementować IDisposable. Garbage Collector sam zadba o wszystkie te pola.

Servy
źródło
1
Zgadzam się z tym - istnieje koncepcja pozbycia się wszystkiego, gdy w rzeczywistości nie jest to potrzebne. Dispose powinno być używane tylko wtedy, gdy masz niezarządzane zasoby do wyczyszczenia !!
Chandramouleswaran Ravichandra
4
Nie do końca prawda, metoda Dispose pozwala również na pozbycie się „zarządzanych zasobów, które implementują IDisposable”
Matt Wilko
@MattWilko To sprawia, że ​​jest to pośredni sposób usuwania niezarządzanych zasobów, ponieważ pozwala innemu zasobowi na pozbycie się niezarządzanego zasobu. Tutaj nie ma nawet pośredniego odwołania do niezarządzanego zasobu za pośrednictwem innego zarządzanego zasobu.
Servy
@MattWilko Dispose automatycznie wywoła dowolny zarządzany zasób, który zaimplementował IDesposable
panky
@pankysharma Nie, nie będzie. Należy go wywołać ręcznie . O to właśnie chodzi. Nie możesz założyć, że zostanie wywołany automatycznie, wiesz tylko, że ludzie powinni dzwonić ręcznie, ale ludzie popełniają błędy i zapominają.
Servy
14

Musisz użyć wzoru jednorazowego w następujący sposób:

private bool _disposed = false;

protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing)
        {
            // Dispose any managed objects
            // ...
        }

        // Now disposed of any unmanaged objects
        // ...

        _disposed = true;
    }
}

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

// Destructor
~YourClassName()
{
    Dispose(false);
}
Belogix
źródło
1
czy nie byłoby mądrzejsze wywołanie GC.SuppressFinalize (this) również w destruktorze? W przeciwnym razie sam obiekt zostałby odzyskany w następnym KG
Sudhanshu Mishra
2
@dotnetguy: Destruktor obiektów jest wywoływany po uruchomieniu gc. Więc dzwonienie dwa razy nie jest możliwe. Zobacz tutaj: msdn.microsoft.com/en-us/library/ms244737.aspx
schoetbi
1
Czy więc teraz nazywamy dowolny fragment kodu standardowego „wzorcem”?
Chel
4
@rdhs Nie, nie jesteśmy. MSDN stwierdza, że jest to wzorzec „Dispose Pattern” tutaj - msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx więc przed głosowaniem w dół może trochę Google?
Belogix
2
Ani Microsoft, ani Twój post nie określają jasno, dlaczego wzorzec ma wyglądać tak. Ogólnie rzecz biorąc, nie jest to nawet szablonowy, jest po prostu zbyteczny - zastępowany przez SafeHandle(i podtypy). W przypadku zarządzanych zasobów wdrożenie odpowiedniej utylizacji staje się znacznie prostsze; możesz przyciąć kod do prostej implementacji void Dispose()metody.
BatteryBackupUnit
10

Nie musisz zajmować się swoją Userklasą, IDisposableponieważ nie pobiera ona żadnych niezarządzanych zasobów (plików, połączeń z bazą danych itp.). Zwykle oznaczamy klasy tak, IDisposablejakby miały przynajmniej jedno IDisposablepole lub / i właściwość. Podczas implementacji IDisposablelepiej ująć to zgodnie z typowym schematem Microsoftu:

public class User: IDisposable {
  ...
  protected virtual void Dispose(Boolean disposing) {
    if (disposing) {
      // There's no need to set zero empty values to fields 
      // id = 0;
      // name = String.Empty;
      // pass = String.Empty;

      //TODO: free your true resources here (usually IDisposable fields)
    }
  }

  public void Dispose() {
    Dispose(true);

    GC.SuppressFinalize(this);
  } 
}
Dmitrij Bychenko
źródło
Tak jest zwykle . Z drugiej strony, konstrukcja using otwiera możliwość napisania czegoś podobnego do inteligentnych wskaźników w C ++, a mianowicie obiektu, który przywróci poprzedni stan bez względu na to, w jaki sposób zostanie zakończony blok using. Jedyny sposób, w jaki udało mi się to zrobić, to uczynić taki obiekt implementacją IDisposable. Wygląda na to, że mogę zignorować ostrzeżenie kompilatora w tak marginalnym przypadku użycia.
Papa Smurf
3

Idisposable jest implementowane zawsze, gdy chcesz deterministycznego (potwierdzonego) wyrzucania elementów bezużytecznych.

class Users : IDisposable
    {
        ~Users()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
            // This method will remove current object from garbage collector's queue 
            // and stop calling finilize method twice 
        }    

        public void Dispose(bool disposer)
        {
            if (disposer)
            {
                // dispose the managed objects
            }
            // dispose the unmanaged objects
        }
    }

Podczas tworzenia i używania klasy Users użyj bloku "using", aby uniknąć jawnego wywoływania metody dispose:

using (Users _user = new Users())
            {
                // do user related work
            }

koniec using bloku utworzonego obiektu Users zostanie usunięty przez niejawne wywołanie metody dispose.

S.Roshanth
źródło
2

Widzę wiele przykładów wzorca Microsoft Dispose, który jest tak naprawdę anty-wzorcem. Jak wielu zwróciło uwagę, kod w pytaniu w ogóle nie wymaga IDisposable. Ale jeśli zamierzasz to zaimplementować, nie używaj wzorca Microsoft. Lepszą odpowiedzią byłoby postępowanie zgodnie z sugestiami zawartymi w tym artykule:

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

Jedyną inną rzeczą, która prawdopodobnie byłaby pomocna, jest pominięcie tego ostrzeżenia dotyczącego analizy kodu ... https://docs.microsoft.com/en-us/visualstudio/code-quality/in-source-suppression-overview?view=vs- 2017

MikeJ
źródło