Реализация 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 Внедрение IDisposable правильно Предоставьте переопределяемую реализацию Dispose (bool) для «User» или пометьте тип как закрытый. Вызов Dispose (false) должен только очистить собственные ресурсы. Вызов Dispose (true) должен очистить как управляемые, так и собственные ресурсы. stman User.cs 10

Для справки: CA1063: правильно реализовать ID

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

Если кто-то может объяснить более ламенными терминами, в чем заключается проблема и / или как должна реализовываться IDisposable, это действительно поможет!


1
Это весь код внутри Dispose?
Клаудио Реди

42
Вы должны реализовать свой метод Dispose () для вызова метода Dispose () любого члена вашего класса. Ни у кого из тех нет ни одного. Поэтому вы не должны реализовывать IDisposable. Сброс значений свойств не имеет смысла.
Ганс Пассант

13
Внедрение необходимо только в том IDispoableслучае, если у вас есть неуправляемые ресурсы, которыми можно распоряжаться (это включает неуправляемые ресурсы, которые обернуты ( SqlConnection, FileStreamи т. Д.). Вы не реализуете и не должны внедрять, IDisposableесли у вас есть только такие управляемые ресурсы, как здесь. Это, IMO, основная проблема анализа кода. Он очень хорош для проверки маленьких глупых правил, но не хорош для проверки концептуальных ошибок
Джейсон

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. если мне нужен только класс в одной или двух функциях
Ortund

62
@ Ortund Вы неправильно поняли. Лучше всего использовать usingблок, когда класс реализует IDisposable . Если вам не нужен класс для одноразового использования, не используйте его. Это не имеет смысла.
Даниэль Манн

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

1
В качестве дополнительного примечания, если вы хотите освободить какие-либо неуправляемые ресурсы, вы должны включить Finalizer, вызывающий Dispose (false), который позволит GC вызывать Finalizer при выполнении сборки мусора (в случае, если Dispose еще не был вызван) и надлежащим образом освобождает неуправляемые Ресурсы.
Мариозски

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

57

Прежде всего, вам не нужно «убирать» strings и ints - они будут автоматически обработаны сборщиком мусора. Единственное, что нужно очистить, 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, имея флажок, чтобы удостовериться, что код очистки выполняется только один раз - это намного лучше, чем установка свойств в null или что-то еще (особенно если это мешает readonlyсемантике)
Thomas

+1 за использование кода пользователя (даже если он будет очищен автоматически), чтобы было понятно, что там происходит. Кроме того, за то, что он не был соленым моряком и избивал его за то, что он сделал небольшую ошибку, учась, как и многие другие здесь.
Murphybro2

42

В следующем примере показаны общие рекомендации по реализации IDisposableинтерфейса. Ссылка

Имейте в виду, что вам нужен деструктор (финализатор), только если у вас есть неуправляемые ресурсы в вашем классе. И если вы добавите деструктор, вы должны запретить Финализацию в Dispose , иначе это приведет к тому, что ваши объекты будут находиться в памяти в течение двух циклов мусора (Примечание: прочтите, как работает Финализация ). Ниже приведен пример.

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 Это делает косвенный способ избавления от неуправляемых ресурсов, потому что это позволяет другому ресурсу распоряжаться неуправляемым ресурсом. Здесь нет даже косвенной ссылки на неуправляемый ресурс через другой управляемый ресурс.
Servy

@MattWilko Dispose будет автоматически вызываться на любом управляемом ресурсе, который внедрил IDesposable
panky

@pankysharma Нет, не будет. Это должно быть вызвано вручную . В этом весь смысл. Вы не можете предполагать, что он будет вызываться автоматически, вы только знаете, что люди должны вызывать его вручную, но люди делают ошибки и забывают.
Serv

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 (this) в деструкторе? В противном случае сам объект был бы восстановлен в следующем GC
Судханшу Мишра

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 мало?
Белогикс

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

Это обычно бывает. Но с другой стороны, конструкция using открывает возможность написания чего-то похожего на интеллектуальные указатели C ++, а именно объекта, который будет восстанавливать предыдущее состояние независимо от того, как завершается использование блока using. Единственный способ, который я нашел, это сделать такой объект реализующим 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
        }
    }

При создании и использовании класса Users используйте блок «using», чтобы избежать явного вызова метода dispose:

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

конец созданного блока пользователя Объект Users будет удален неявным вызовом метода dispose.


2

Я вижу много примеров шаблона Microsoft Dispose, который действительно является анти-шаблоном. Как многие отмечают, код в вопросе не требует IDisposable вообще. Но если вы собираетесь его реализовать, не используйте шаблон 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.