Правильное использование интерфейса IDisposable

Из документации Microsoft я узнал, что "основное" использование интерфейса IDisposable - очистка неуправляемых ресурсов.

Для меня "неуправляемый" означает такие вещи, как соединения с базой данных, сокеты, дескрипторы окон и т.д. Но я видел код, в котором реализован метод Dispose() для освобождения управляемых ресурсов, что мне кажется избыточным, поскольку сборщик мусора должен позаботься об этом за тебя.

Например:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

У меня такой вопрос: делает ли сборщик мусора свободной памятью, используемой MyCollection, быстрее, чем обычно?

edit: Пока что люди опубликовали несколько хороших примеров использования IDisposable для очистки неуправляемых ресурсов, таких как соединения с базой данных и растровые изображения. Но предположим, что _theList в приведенном выше коде содержит миллион строк, и вы хотели освободить эту память сейчас, а не ждать сборщика мусора. Будет ли приведенный выше код выполнить это?

Ответ 1

Задача Dispose - освободить неуправляемые ресурсы. Это нужно сделать в какой-то момент, иначе они никогда не будут очищены. Сборщик мусора не знает, как вызвать DeleteHandle() для переменной типа IntPtr, он не знает, нужно ли ему вызывать DeleteHandle().

Примечание. Что такое неуправляемый ресурс? Если вы нашли его в Microsoft.NET Framework: это удалось. Если вы сами ковырялись в MSDN, это неуправляемо. Все, что вы использовали с помощью вызовов P/Invoke, чтобы выйти из приятного удобного мира всего, что доступно вам в .NET Framework, неуправляемо - и теперь вы несете ответственность за его очистку.

Созданный вами объект должен предоставить некоторый метод, который может вызывать внешний мир, для очистки неуправляемых ресурсов. Метод можно назвать как угодно:

public void Cleanup()

или же

public void Shutdown()

Но вместо этого есть стандартизированное имя для этого метода:

public void Dispose()

Был даже создан интерфейс IDisposable, который имеет только один метод:

public interface IDisposable
{
   void Dispose()
}

Таким образом, вы заставляете ваш объект предоставлять интерфейс IDisposable, и таким образом вы обещаете, что написали этот единственный метод для очистки ваших неуправляемых ресурсов:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

И вы сделали. За исключением того, что вы можете сделать лучше.


Что если ваш объект выделил 250MB System.Drawing.Bitmap (то есть, управляемый .NET класс Bitmap) в качестве буфера кадра? Конечно, это управляемый объект .NET, и сборщик мусора освободит его. Но вы действительно хотите оставить 250 МБ памяти, просто сидя там - ожидая, когда сборщик мусора в конце концов придет и освободит его? Что делать, если есть открытое соединение с базой данных? Конечно, мы не хотим, чтобы это соединение оставалось открытым, ожидая, пока GC завершит объект.

Если пользователь вызвал Dispose() (то есть он больше не планирует использовать объект), почему бы не избавиться от этих расточительных растровых изображений и соединений с базой данных?

Итак, теперь мы будем:

  • избавиться от неуправляемых ресурсов (потому что мы должны), и
  • избавиться от управляемых ресурсов (потому что мы хотим быть полезными)

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

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

И все хорошо, кроме того, что вы можете сделать лучше !


Что если человек забыл вызвать Dispose() для вашего объекта? Тогда они будут пропускать некоторые неуправляемые ресурсы!

Примечание. Они не будут пропускать управляемые ресурсы, поскольку в конечном итоге сборщик мусора будет работать в фоновом потоке и освобождать память, связанную с любыми неиспользуемыми объектами. Это будет включать ваш объект и любые управляемые объекты, которые вы используете (например, Bitmap и DbConnection).

Если человек забыл вызвать Dispose(), мы все равно можем сохранить его бекон! У нас все еще есть способ вызвать это для них: когда сборщик мусора, наконец, приступает к освобождению (т.е. завершению) нашего объекта.

Примечание. Сборщик мусора в конечном итоге освободит все управляемые объекты. Когда это происходит, он вызывает метод Finalize для объекта. GC не знает или не заботится о вашем методе утилизации. Это было просто имя, которое мы выбрали для метода, который мы вызываем, когда хотим избавиться от неуправляемых вещей.

Уничтожение нашего объекта сборщиком мусора - идеальное время, чтобы освободить эти надоедливые неуправляемые ресурсы. Мы делаем это путем переопределения метода Finalize().

Примечание. В С# вы явно не переопределяете метод Finalize(). Вы пишете метод, который выглядит как деструктор C++, а компилятор принимает это за реализацию метода Finalize():

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

Но в этом коде есть ошибка. Видите ли, сборщик мусора работает в фоновом потоке; Вы не знаете порядок, в котором уничтожены два объекта. Вполне возможно, что в вашем коде Dispose() управляемого объекта, от которого вы пытаетесь избавиться (потому что вы хотели быть полезным), больше нет:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

Так что вам нужно, чтобы Finalize() сказал Dispose() что он не должен касаться каких-либо управляемых ресурсов (потому что их там больше не может быть), при этом освобождая неуправляемые ресурсы.

Стандартный шаблон для этого состоит в том, чтобы Finalize() и Dispose() вызывали третий (!) Метод; где вы передаете логическое выражение, если вы вызываете его из Dispose() (в отличие от Finalize()), то есть безопасно освобождать управляемые ресурсы.

Этому внутреннему методу может быть присвоено произвольное имя, например "CoreDispose" или "MyInternalDispose", но принято называть его Dispose(Boolean):

protected void Dispose(Boolean disposing)

Но более полезное имя параметра может быть:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

И вы измените свою реализацию метода IDisposable.Dispose() на:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it safe
}

и ваш финализатор для:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it *not* safe
}

Примечание. Если ваш объект происходит от объекта, который реализует Dispose, не забудьте вызвать базовый метод Dispose при переопределении Dispose:

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

И все хорошо, кроме того, что вы можете сделать лучше !


Если пользователь вызывает Dispose() для вашего объекта, то все было очищено. Позже, когда сборщик мусора приходит и вызывает Finalize, он снова вызывает Dispose.

Это не только расточительно, но если у вашего объекта есть ненужные ссылки на объекты, которые вы уже удалили из последнего вызова Dispose(), вы попытаетесь утилизировать их снова!

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

Когда пользователь вызывает Dispose(): дескриптор CursorFileBitmapIconServiceHandle уничтожается. Позже, когда запускается сборщик мусора, он снова попытается уничтожить ту же ручку.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

Способ исправить это - сказать сборщику мусора, что ему не нужно беспокоиться о завершении объекта - его ресурсы уже очищены, и больше не требуется никакой работы. Вы делаете это, вызывая GC.SuppressFinalize() в методе Dispose():

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Теперь, когда пользователь вызвал Dispose(), мы имеем:

  • освобожденные неуправляемые ресурсы
  • освобожденные управляемые ресурсы

В GC нет смысла запускать финализатор - обо всем позаботились.

Не могу ли я использовать Finalize для очистки неуправляемых ресурсов?

Документация для Object.Finalize гласит:

Метод Finalize используется для выполнения операций по очистке неуправляемых ресурсов, удерживаемых текущим объектом, до его уничтожения.

Но документация MSDN также говорит, для IDisposable.Dispose:

Выполняет определенные пользователем задачи, связанные с освобождением, освобождением или сбросом неуправляемых ресурсов.

Так что это? Какое место для меня, чтобы очистить неуправляемые ресурсы? Ответ:

Это твой выбор! Но выберите Dispose.

Вы, конечно, можете поместить свою неуправляемую очистку в финализатор:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

Проблема в том, что вы понятия не имеете, когда сборщик мусора дойдет до завершения вашего объекта. Ваши неуправляемые, ненужные, неиспользованные нативные ресурсы будут зависать до тех пор, пока сборщик мусора не будет запущен. Затем он вызовет ваш метод финализатора; очистка неуправляемых ресурсов. Документация Object.Finalize указывает на это:

Точное время выполнения финализатора не определено. Чтобы обеспечить детерминированное освобождение ресурсов для экземпляров вашего класса, реализуйте метод Close или предоставьте реализацию IDisposable.Dispose.

Это преимущество использования Dispose для очистки неуправляемых ресурсов; Вы узнаете и контролируете, когда неуправляемые ресурсы очищаются. Их уничтожение является "детерминированным".


Чтобы ответить на ваш первоначальный вопрос: почему бы не освободить память сейчас, а не тогда, когда GC решит это сделать? У меня есть программное обеспечение для распознавания лица, которая должна избавиться от 530 МБ внутренних образов в настоящее время, так как они больше не нужны. Когда мы этого не делаем: машина останавливается.

Чтение бонусов

Для тех, кто любит стиль этого ответа (объясняя почему, так и как становится очевидным), я предлагаю вам прочитать Главу Один из Don Box Essential COM:

На 35 страницах он объясняет проблемы использования бинарных объектов и изобретает COM на ваших глазах. Как только вы поймете причину COM, оставшиеся 300 страниц станут очевидными и просто детализируют реализацию Microsoft.

Я думаю, что каждый программист, который когда-либо имел дело с объектами или COM, должен, по крайней мере, прочитать первую главу. Это лучшее объяснение чего-либо.

Дополнительное чтение бонусов

Когда все, что ты знаешь, неправильно Эриком Липпертом

Поэтому действительно очень сложно написать правильный финализатор, и лучший совет, который я могу вам дать, - не пытаться.

Ответ 2

IDisposable часто используется для использования оператора using и использует простой способ сделать детерминированную очистку управляемых объектов.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

Ответ 3

Цель шаблона Dispose - предоставить механизм для очистки как управляемых, так и неуправляемых ресурсов, и когда это произойдет, зависит от способа вызова метода Dispose. В вашем примере использование Dispose на самом деле не делает ничего, что связано с удалением, так как очистка списка не влияет на размещение этой коллекции. Аналогично, вызовы для установки переменных в null также не влияют на GC.

Вы можете ознакомиться с этой статьей для более подробной информации о том, как реализовать шаблон Dispose, но в основном выглядит так:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

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

Наиболее важным здесь является Dispose (bool), который фактически работает в двух разных обстоятельствах:

  • disposing == true: метод был вызван прямо или косвенно с помощью кода пользователя. Управляемые и неуправляемые ресурсы могут быть удалены.
  • disposing == false: метод был вызван средой выполнения внутри финализатора, и вы не должны ссылаться на другие объекты. Могут быть удалены только неуправляемые ресурсы.

Проблема простое, чтобы GC заботиться о том, чтобы очистить, состоит в том, что у вас нет реального контроля, когда GC будет запускать цикл сбора (вы можете вызвать GC.Collect(), но вы действительно не должны этого делать) ресурсы могут оставаться дольше, чем необходимо. Помните, что вызов Dispose() фактически не вызывает цикл сбора или каким-либо образом заставляет GC собирать/освобождать объект; он просто предоставляет средства для более детерминированной очистки используемых ресурсов и сообщает GC, что эта очистка уже выполнена.

Весь смысл IDisposable и шаблона dispose заключается не в немедленном освобождении памяти. Единственный раз, когда вызов Dispose на самом деле даже имеет шанс немедленно освободить память, когда он обрабатывает сценарий dispose == false и манипулирует неуправляемыми ресурсами. Для управляемого кода память фактически не будет восстановлена ​​до тех пор, пока GC не проведет цикл сбора, который вы действительно не контролируете (кроме вызова GC.Collect(), о котором я уже говорил, это не очень хорошая идея).

Ваш сценарий действительно недействителен, поскольку строки в .NET не используют какие-либо неизведанные ресурсы и не реализуют IDisposable, нет способа заставить их "очищаться".

Ответ 4

Не должно быть никаких дальнейших вызовов объектных методов после вызова Dispose (хотя объект должен терпеть дальнейшие вызовы Dispose). Поэтому пример в вопросе глупо. Если Dispose вызывается, то сам объект может быть отброшен. Таким образом, пользователь должен просто отбросить все ссылки на весь этот объект (установить их в null), и все связанные с ним объекты будут автоматически очищены.

Что касается общего вопроса об управляемом/неуправляемом и обсуждении в других ответах, я думаю, что любой ответ на этот вопрос должен начинаться с определения неуправляемого ресурса.

Что сводится к тому, что есть функция, которую вы можете вызвать, чтобы привести систему в состояние, и есть еще одна функция, которую вы можете вызвать, чтобы вывести ее из этого состояния. Теперь, в типичном примере, первая может быть функцией, которая возвращает дескриптор файла, а второй - вызовом CloseHandle.

Но - и это ключ - они могут быть любой подходящей парой функций. Один строит состояние, другой срывает его. Если государство было построено, но не снесено, то существует экземпляр ресурса. Вам необходимо организовать разрывы в нужное время - ресурс не управляется CLR. Единственным автоматически управляемым типом ресурса является память. Существует два вида: GC и стек. Типы значений управляются стеком (или путем приведения в движение внутри ссылочных типов), а ссылочные типы управляются GC.

Эти функции могут приводить к изменениям состояния, которые могут свободно перемежаться или, возможно, должны быть полностью вложенными. Изменения состояния могут быть потокобезопасными, или они могут не быть.

Посмотрите на пример в вопросе "Правосудие". Изменения в отладке файла журнала должны быть полностью вложенными, или все идет не так. Также они вряд ли будут потокобезопасными.

Можно собрать поездку с сборщиком мусора, чтобы очистить неуправляемые ресурсы. Но только если функции изменения состояния являются потокобезопасными, а два состояния могут иметь периоды жизни, которые перекрываются каким-либо образом. Итак, пример юстиции ресурса НЕ должен иметь финализатор! Это никому не помогло.

Для этих ресурсов вы можете просто реализовать IDisposable без финализатора. Финализатор абсолютно необязателен - это должно быть. Это замалчивается или даже не упоминается во многих книгах.

Затем вам нужно использовать оператор using, чтобы иметь возможность гарантировать, что вызывается Dispose. Это по существу похоже на то, что вы едете со стеком (так как финализатор относится к GC, using относится к стеку).

Недопустимая часть состоит в том, что вам нужно вручную записать Dispose и заставить ее вызывать свои поля и ваш базовый класс. Программистам С++/CLI этого не нужно. В большинстве случаев компилятор записывает их для них.

Есть альтернатива, которую я предпочитаю для состояний, которые идеально встраиваются и не являются потокобезопасными (кроме всего прочего, избегая IDisposable spares, вы имеете проблему с аргументом с кем-то, кто не может удержаться от добавления финализатора для каждого класса, который реализует IDisposable).

Вместо написания класса вы пишете функцию. Функция принимает делегата для обратного вызова:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

И тогда простой пример:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

Пропускаемая лямбда служит в качестве кодового блока, поэтому, как и вы, ваша собственная структура управления служит той же цели, что и using, за исключением того, что у вас больше нет опасности, что вызывающий абонент злоупотребляет ею. Нет способа, чтобы они не смогли очистить ресурс.

Этот метод менее полезен, если ресурс является видом, который может иметь перекрывающиеся сроки жизни, потому что тогда вы хотите иметь возможность создавать ресурс A, затем ресурс B, а затем убивать ресурс A, а затем убивать ресурс B. Вы можете ' t сделать это, если вы заставили пользователя идеально вложить такие гнезда. Но тогда вам нужно использовать IDisposable (но все же без финализатора, если только вы не реализовали потоки, которые не являются бесплатными).

Ответ 5

Сценарии Я использую IDisposable: очистка неуправляемых ресурсов, отмена подписки на события, закрытые соединения

Идиома, которую я использую для реализации IDisposable (not threadsafe):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

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

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

Ответ 6

Да, этот код является полностью избыточным и ненужным, и он не делает сборщик мусора делать что-либо, что он не сделал бы иначе (если экземпляр MyCollection выходит за пределы области видимости.) Особенно вызовы .Clear().

Отвечайте на свое редактирование: Сортировка. Если я это сделаю:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Он функционально идентичен этому для управления памятью:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Если вам действительно действительно нужно освободить память в этот самый момент, вызовите GC.Collect(). Однако здесь нет оснований делать это. При необходимости память будет освобождена.

Ответ 7

Если MyCollection собирается собирать мусор в любом случае, вам не нужно его утилизировать. Это приведет к простому откату CPU больше, чем необходимо, и может даже аннулировать некоторый предварительно вычисленный анализ, который уже сделал сборщик мусора.

Я использую IDisposable для выполнения таких действий, как обеспечение корректного удаления потоков, а также неуправляемых ресурсов.

EDIT В ответ на комментарий Скотта:

Единственный момент, когда влияют показатели производительности GC, - это когда вызывается [sic] GC.Collect() "

Концептуально, GC поддерживает представление о графе ссылок на объекты и все ссылки на него из фреймов стека потоков. Эта куча может быть довольно большой и охватывать многие страницы памяти. В качестве оптимизации GC анализирует страницы, которые вряд ли будут меняться очень часто, чтобы избежать повторного сканирования страницы без необходимости. GC получает уведомление от ядра, когда данные на странице меняются, поэтому он знает, что страница загрязнена и требует повторного сканирования. Если коллекция находится в Gen0, то вероятно, что другие вещи на странице тоже меняются, но это менее вероятно в Gen1 и Gen2. Анекдотически эти крючки не были доступны в Mac OS X для команды, которая портировала GC на Mac, чтобы получить подключаемый модуль Silverlight, работающий на этой платформе.

Еще один момент против ненужной утилизации ресурсов: представьте ситуацию, когда процесс выгружается. Представьте также, что этот процесс работает некоторое время. Скорее всего, многие из этих страниц памяти процесса были заменены на диск. По крайней мере, они больше не находятся в кешках L1 или L2. В такой ситуации нет смысла использовать приложение для разгрузки для замены всех этих данных и кодовых страниц в память для "освобождения" ресурсов, которые в любом случае будут освобождены операционной системой, когда процесс завершится. Это относится к управляемым и даже к неуправляемым ресурсам. Только ресурсы, которые поддерживают не-фоновые потоки, должны быть удалены, иначе процесс останется в живых.

Теперь во время обычного выполнения есть эфемерные ресурсы, которые необходимо очистить правильно (поскольку @fezmonkey указывает соединения с базой данных, сокеты, дескрипторы окон), чтобы избежать неуправляемых утечек памяти. Это те вещи, которые нужно уничтожить. Если вы создаете какой-то класс, которому принадлежит поток (и по собственному я имею в виду, что он его создал, и поэтому он отвечает за то, чтобы он остановился, по крайней мере, по моему стилю кодирования), тогда этот класс, скорее всего, должен реализовать IDisposable и снести нить во время Dispose.

.NET framework использует интерфейс IDisposable как сигнал, даже предупреждающий разработчиков, что этот класс должен быть удален. Я не могу думать о каких-либо типах в рамках, реализующих IDisposable (исключая явные реализации интерфейса), где удаление необязательно.

Ответ 9

В приведенном выше примере он по-прежнему не "освобождает память сейчас". Вся память собрана мусором, но она может позволить сбор памяти в более раннем

Итак, вот несколько конкретных примеров, которые не делают никакой неуправляемой очистки ресурсов, но успешно используют IDisposable для создания более чистого кода.

Ответ 10

Я не буду повторять обычные вещи о том, как использовать или освобождать неуправляемые ресурсы, которые все были покрыты. Но я хотел бы указать, что кажется распространенным заблуждением. Учитывая следующий код

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

  Public Sub Dispose() Implements IDisposable.Dispose
    _Large=Nothing
  End Sub

Я понимаю, что одноразовая реализация не соответствует текущим рекомендациям, но, надеюсь, вы все поняли. Теперь, когда вызывается Dispose, сколько памяти освобождается?

Ответ: Нет.
Вызов Dispose может освобождать неуправляемые ресурсы, он НЕ МОЖЕТ восстановить управляемую память, только GC может это сделать. Это не означает, что вышеизложенное не является хорошей идеей, следуя приведенной выше схеме, по-прежнему остается хорошей идеей. После того, как Dispose был запущен, ничего не мешает GC повторно запрашивать память, которая используется _Large, хотя экземпляр LargeStuff все еще может быть в области видимости. Строки в _Large также могут быть в гене 0, но экземпляр LargeStuff может быть gen 2, поэтому снова память будет повторно заявлена ​​раньше.
Нет смысла добавлять финализатора для вызова метода Dispose, показанного выше. Это будет просто ЗАДЕРЖАТЬ повторное требование к памяти, чтобы позволить финализатору работать.

Ответ 11

Помимо основного использования в качестве средства управления временем жизни системных ресурсов system resources (полностью охваченного удивительным ответом Иана, слава!), IDisposable/using Комбо также можно использовать для определения изменения состояния (критических) глобальных ресурсов: консоли, потоков, процесса, любого глобального объекта, например экземпляра приложения.

Я написал статью об этом шаблоне: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

Он иллюстрирует, как вы можете защитить некоторые часто используемые глобальные состояния с помощью многоразового использования и читабельного: цвета консоли, текущую культуру потоков, свойства объекта приложения Excel...

Ответ 12

Во всяком случае, я ожидаю, что код будет менее эффективным, чем при его отсутствии.

Вызов методов Clear() не нужен, и GC, вероятно, не сделал бы этого, если Dispose не сделал этого...

Ответ 13

Есть вещи, которые операция Dispose() делает в примере кода, который может иметь эффект, который не будет происходить из-за обычного GC объекта MyCollection.

Если объекты, на которые ссылаются _theList или _theDict, ссылаются на другие объекты, тогда объект List<> или Dictionary<> не будет подлежать сбору, но не будет иметь никакого содержимого. Если бы не было операции Dispose(), как в примере, эти коллекции все равно будут содержать их содержимое.

Конечно, если бы это была ситуация, я бы назвал ее сломанным дизайном - я просто указываю (по-видимому, педантично), что операция Dispose() может быть не полностью избыточной, в зависимости от того, существуют ли другие виды использования из List<> или Dictionary<>, которые не показаны в фрагменте.

Ответ 14

IDisposable подходит для отмены подписки на события.

Ответ 15

Одна из проблем большинства обсуждений "неуправляемых ресурсов" заключается в том, что они действительно не определяют этот термин, но, похоже, подразумевают, что он имеет какое-то отношение к неуправляемому коду. Хотя верно, что многие типы неуправляемых ресурсов взаимодействуют с неуправляемым кодом, мышление о неуправляемых ресурсах в таких терминах не помогает.

Вместо этого следует понимать, что общего у всех управляемых ресурсов: все они влекут за собой объект, запрашивающий у кого-то вне "вещи" что-то делать от его имени, в ущерб каким-то другим "вещам", а другое лицо соглашается сделайте это до дальнейшего уведомления. Если объект должен был быть оставлен и исчезнут без следа, ничто не могло бы сказать, что вне "вещи" ему больше не нужно было изменять свое поведение от имени объекта, который больше не существовал; следовательно, "полезность вещей будет постоянно уменьшаться".

Таким образом, неуправляемый ресурс представляет собой соглашение какой-либо внешней "вещи", чтобы изменить свое поведение от имени объекта, что бесполезно ухудшало бы полезность этой внешней "вещи", если бы объект был оставлен и прекратил свое существование. Управляемый ресурс - это объект, который является бенефициаром такого соглашения, но который подписал для получения уведомления, если он оставлен, и который будет использовать такое уведомление, чтобы привести свои дела в порядок до его уничтожения.

Ответ 16

Первое из определения. Для меня неуправляемый ресурс означает некоторый класс, который реализует интерфейс IDisposable или что-то созданное с использованием вызовов в dll. GC не знает, как бороться с такими объектами. Если класс имеет, например, только типы значений, то я не рассматриваю этот класс как класс с неуправляемыми ресурсами. Для моего кода я следую следующим практикам:

  • Если созданный мной класс использует некоторые неуправляемые ресурсы, значит, я также должен реализовать интерфейс IDisposable для очистки памяти.
  • Очистите объекты, как только я их закончу.
  • В моем методе dispose я перебираю все IDisposable членов класса и вызываю Dispose.
  • В моем методе метода Dispose GC.SuppressFinalize(this), чтобы уведомить сборщика мусора о том, что мой объект уже очищен. Я делаю это, потому что вызов GC - дорогостоящая операция.
  • В качестве дополнительной меры предосторожности я пытаюсь сделать возможным вызов Dispose() несколько раз.
  • Когда-то я добавляю закрытый член _disposed и проверяет вызовы методов, если объект был очищен. И если он был очищен, сгенерируйте ObjectDisposedException
    Следующий шаблон демонстрирует то, что я описал в словах как образец кода:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

Ответ 17

Наиболее оправданным вариантом использования для распоряжения управляемыми ресурсами является подготовка к тому, чтобы GC восстановил ресурсы, которые в противном случае никогда не были бы собраны.

Первым примером является круговая ссылка.

В то время как лучше всего использовать шаблоны, которые избегают циклических ссылок, если вы в конечном итоге получаете (например) "дочерний" объект, у которого есть ссылка на его "родительский", это может остановить сбор GC GC родителя, если вы просто отказываетесь от ссылки и полагаетесь на GC-plus, если вы внедрили финализатор, он никогда не будет вызван.

Единственный способ обойти это вручную, чтобы разбить круговые ссылки, установив ссылки родителя на нуль для детей.

Реализация IDisposable для родителей и детей - лучший способ сделать это. Когда Dispose вызывается в Parent, вызовите Dispose для всех дочерних элементов и в методе Dispose для детей установите для родительских ссылок значение null.

Ответ 18

Ваш образец кода не является хорошим примером использования IDisposable. Обычно очистка словаря не должна идти к методу Dispose. Элементы словаря будут очищены и удалены, когда они выйдут из сферы действия. IDisposable требуется освободить некоторую память/обработчики, которые не будут освобождены/освобождены даже после того, как они выйдут из области видимости.

В следующем примере показан хороший пример для шаблона 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 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.
    }
}

Ответ 19

Я вижу, что многие ответы изменились, чтобы поговорить об использовании IDisposable как для управляемых, так и для неуправляемых ресурсов. Я бы предложил эту статью как одно из лучших объяснений того, как на самом деле следует использовать IDisposable.

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

Для актуального вопроса; Если вы используете IDisposable для очистки управляемых объектов, которые занимают много памяти, короткий ответ будет отрицательным. Причина в том, что после удаления IDisposable вы должны позволить ему выйти из области видимости. На этом этапе любые дочерние объекты, на которые ссылаются, также находятся вне области видимости и будут собраны.

Единственное реальное исключение - это если у вас много памяти, связанной в управляемых объектах, и вы заблокировали этот поток, ожидая завершения какой-либо операции. Если эти объекты не понадобятся после завершения этого вызова, то установка этих ссылок на null может позволить сборщику мусора собирать их раньше. Но этот сценарий будет представлять плохой код, который необходимо реорганизовать, а не вариант использования IDisposable.