Правильна реалізація IDisposable


145

У своїх класах я реалізую IDisposable наступним чином:

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;
    }
}

У VS2012 мій аналіз коду говорить, що правильно реалізувати IDisposable, але я не впевнений, що я тут зробив неправильно.
Точний текст такий:

CA1063 Правильно реалізувати ID Забезпечити реалізацію реалізованої версії Dispose (bool) на "User" або позначте тип як запечатаний. Заклик розпоряджатися (помилковим) повинен очищати лише рідні ресурси. Заклик розпоряджатися (правда) повинен очистити як керовані, так і рідні ресурси. stman User.cs 10

Для довідки: CA1063: Правильно реалізуйте ідентифікатор

Я прочитав цю сторінку, але боюся, що я не дуже розумію, що тут потрібно зробити.

Якщо хтось може пояснити більш ламенними термінами, в чому полягає проблема та / або як слід реалізувати IDisposable, це дійсно допоможе!


1
Це все в коді всередині Dispose?
Клаудіо Реді

42
Ви повинні реалізувати свій метод Dispose () для виклику методу Dispose () для будь-якого з членів вашого класу. Жоден з цих членів не має жодного. Тому вам не слід застосовувати IDisposable. Скидання значень властивості безглуздо.
Ганс Пасант

13
Вам потрібно реалізувати, лише IDispoableякщо у вас є некеровані ресурси для розпорядження (сюди входять некеровані ресурси, які завернуті ( SqlConnection, FileStreamі т. Д.). Ви не повинні і не повинні реалізовувати, IDisposableякщо у вас є лише керовані ресурси, наприклад, тут). ІМО, Основна проблема в аналізі коду. Дуже добре перевіряти нерозумні правила, але не дуже добре перевіряти концептуальні помилки
Jason

51
Мене цілком засмучує те, що деякі люди скоріше заступаються і бачать це питання закритим, ніж намагаються допомогти людині, яка явно неправильно зрозуміла концепцію. Який сором.
Ортунд

2
Тож не зволікайте, не заявляйте, залиште повідомлення на нулі і закрийте питання корисним вказівником.
tjmoore

Відповіді:


113

Це була б правильна реалізація, хоча я не бачу нічого необхідного для розпорядження в опублікованому вами коді. Вам потрібно реалізувати лише IDisposableтоді, коли:

  1. У вас є некеровані ресурси
  2. Ви тримаєтесь за посиланнями на речі, які самі по собі є одноразовими.

Нічого в коді, який ви опублікували, не потрібно утилізувати.

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

2
Мені сказали, коли я почав писати на C #, що найкраще використовувати, using(){ }коли це можливо, але для цього потрібно реалізувати IDisposable, тому загалом я вважаю за краще отримати доступ до класу через usings, esp. якщо мені потрібен лише клас в одній або двох функціях
Ортунд

62
@Ortund Ви неправильно зрозуміли. Найкраще використовувати usingблок, коли клас реалізує IDisposable . Якщо вам не потрібен клас для одноразового використання, не застосовуйте його. Це не виконує ніякої мети.
Даніель Манн

5
@DanielMann Семантика usingблоку, як правило, приваблива поза IDisposableінтерфейсом, однак. Я гадаю, було більше кількох зловживань IDisposableлише з метою визначення обсягу.
Томас

1
Як бічна примітка, якщо у вас будуть звільнені будь-які некеровані ресурси, ви повинні включити Finalizer, що викликає Dispose (false), що дозволить GC викликати Finalizer під час збору сміття (у випадку, якщо утилізація ще не зателефонувала) та належним чином звільнити некерований ресурси.
mariozski

4
Без фіналізатора у вашій реалізації дзвінки GC.SuppressFinalize(this);безглуздо. Як @mariozski зазначив фіналізатор допоможе забезпечити , що Disposeназивається на всіх , якщо клас не використовується всередині usingблоку.
Хаймо Кутчбах

57

Перш за все, вам не потрібно "прибирати" stringі int- вони будуть автоматично опікуватися сміттєзбірником. Єдине, що потрібно очистити, - Disposeце некеровані ресурси або керовані ресурси, які реалізуються IDisposable.

Однак якщо припустити, що це лише навчання, то рекомендованим способом реалізації IDisposableє додавання «уловлювача безпеки», щоб гарантувати, що будь-які ресурси не будуть утилізовані двічі:

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;   
    }
}

3
+1, встановивши прапор, щоб переконатися, що код очищення виконується лише один раз, це спосіб, який краще, ніж встановлення властивостей на нуль чи будь-що інше (тим більше, що це заважає readonlyсемантиці)
Томас

+1 за використання коду користувачів (навіть якщо він буде очищений автоматично), щоб зрозуміти, що відбувається там. Крім того, за те, що він не був солоним матросом і не забив його за невелику помилку, навчаючись, як і багато інших.
Мерфібро2

42

Наступний приклад показує загальну найкращу практику для використання IDisposableінтерфейсу. Довідково

Майте на увазі, що деструктор (фіналізатор) вам потрібен лише в тому випадку, якщо у вас є керовані ресурси. І якщо ви додасте деструктор, вам слід придушити доопрацювання у розпорядженні , інакше це призведе до того, що ваші об'єкти залишаться в пам'яті протягом двох циклів сміття (Примітка. Прочитайте, як працює фіналізація ). Нижче наведено приклад детальніше.

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

14

IDisposableіснує, щоб забезпечити вам засіб для очищення некерованих ресурсів, які не буде очищено автоматично збирачем сміття.

Усі ресурси, які ви "прибираєте", - це керовані ресурси, і тому ваш Disposeметод нічого не досягає. Ваш клас взагалі не повинен реалізовуватись IDisposable. Колекціонер сміття піклується про всі ці поля самостійно.


1
Погодьтеся з цим - існує поняття розпоряджатися усім, коли воно насправді не потрібно. Утилізацію слід використовувати лише в тому випадку, якщо у вас є некеровані ресурси для очищення !!
Chandramouleswaran Ravichandra

4
Не зовсім вірно, метод Dispose також дозволяє розпоряджатися "керованими ресурсами, які реалізують IDisposable"
Метт Вілько

@MattWilko Це робить непрямим способом розпорядження некерованими ресурсами, оскільки він дозволяє іншому ресурсу розпоряджатися некерованим ресурсом. Тут навіть немає непрямого посилання на некерований ресурс через інший керований ресурс.
Сервіс

@MattWilko Dispose автоматично зателефонує на будь-який керований ресурс, який реалізував IDesposable
panky

@pankysharma Ні, не буде. Це потрібно викликати вручну . У цьому вся суть. Ви не можете припустити, що він автоматично викличеться, ви знаєте лише, що люди повинні вручну телефонувати, але люди роблять помилки і забувають.
Сервіс

14

Вам потрібно використовувати одноразовий візерунок так:

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);
}

1
Чи не було б розумнішим дзвінок на GC.SuppressFinalize (це) і в деструкторі? Інакше сам об’єкт буде відтворений у наступному ГК
Судханшу Мішра

2
@dotnetguy: Деструктор об'єктів викликається, коли працює gc. Тож зателефонувати двічі неможливо. Дивіться тут: msdn.microsoft.com/en-us/library/ms244737.aspx
schoetbi

1
Тож тепер ми називаємо будь-який фрагмент кодового коду «візерунком»?
Чел

4
@rdhs Ні, ми не є. MSDN стверджує , що IS шаблон «Dispose шаблон» тут - msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx тому перед вниз голосування , може бути Google мало?
Belogix

2
Ні Microsoft, ні ваш пост чітко не вказують, чому шаблон повинен виглядати таким чином. Взагалі, це навіть не котельня, вона просто зайва - витісняється SafeHandle(і підтипами). У випадку керованих ресурсів реалізація належного розпорядження стає набагато простішою; ви можете обрізати код до простої реалізації void Dispose()методу.
BatteryBackupUnit

10

Вам не потрібно робити свій Userклас, IDisposableоскільки клас не набуває жодних керованих ресурсів (файлів, підключення до бази даних тощо). Зазвичай ми позначаємо класи так, IDisposableніби вони мають принаймні одне IDisposableполе чи / та властивість. Реалізуючи IDisposable, краще покладіть його за типовою схемою Microsoft:

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);
  } 
}

Так зазвичай буває. Але з іншого боку, використовуюча конструкція відкриває можливість запису чогось подібного до розумних покажчиків C ++, а саме об'єкта, який відновить попередній стан незалежно від того, як використовується блок використання. Єдиний спосіб, який я знайшов робити це - зробити такий об'єкт реалізацією IDisposable. Здається, я можу ігнорувати попередження компілятора в такому випадку граничного використання.
Тато Смурф

3

Idisposable - це реалізація, коли потрібно детермінований (підтверджений) збір сміття.

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
        }
    }

Під час створення та використання класу "Користувачі" використовуйте блок "використання", щоб уникнути явного виклику методу розпорядження:

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

кінець використовуваного блоку, створеного користувачем, об'єкт буде розміщений шляхом неявного виклику методу розпорядження.


2

Я бачу дуже багато прикладів схеми Microsoft Dispose, яка справді є антидією. Як багато хто вказав, код у запитанні зовсім не вимагає ідентифікації. Але якщо ви, де збираєтесь його реалізувати, не використовуйте шаблон Microsoft. Кращою відповіддю буде наступні пропозиції в цій статті:

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

Єдине, що може бути корисним - це придушення попередження про аналіз коду ... https://docs.microsoft.com/en-us/visualstudio/code-quality/in-source-suppression-overview?view=vs- 2017 рік

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.