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

Из документации MSDN я узнал, что "основное" использование 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 быстрее, чем обычно?

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

21 ответ

Решение

Задача 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() метод.

Примечание: в C# вы явно не переопределяете 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's safe
}

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

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *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's 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 МБ внутренних изображений, поскольку они больше не нужны. Когда мы этого не делаем: машина останавливается.

Бонус Чтение

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

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

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

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

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

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

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

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

Вы можете взглянуть на эту статью для получения дополнительной информации о том, как реализовать шаблон 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), который на самом деле работает в двух разных обстоятельствах:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Существует альтернатива, которую я предпочитаю для состояний, которые отлично встраиваются и не являются поточно-ориентированными (кроме всего прочего, избегание IDisposable избавляет вас от проблемы с кем-то, кто не может сопротивляться добавлению финализатора в каждый класс, реализующий 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. Вы не можете сделать это если вы заставили пользователя идеально вложить это. Но тогда вам нужно использовать IDisposable (но все еще без финализатора, если вы не реализовали безопасность потоков, что не является бесплатным).

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

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

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
}

Да, этот код является полностью избыточным и ненужным, и он не заставляет сборщик мусора делать то, что он не делал бы иначе (когда экземпляр 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(), Там нет причин делать это здесь, хотя. Память будет освобождена, когда это необходимо.

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

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

РЕДАКТИРОВАТЬ В ответ на комментарий Скотта:

Единственный раз, когда влияют на показатели производительности 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 (исключая явные реализации интерфейса), где удаление является необязательным.

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

Открытый класс LargeStuff
  Реализует IDisposable
  Private _Large as string()

  "Какой-то странный код, который означает, что _Large теперь содержит несколько миллионов длинных строк.

  Public Sub Dispose () Реализует IDisposable.Dispose
    не _Large= Ничто
  End Sub

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

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

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


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

Однажды я прочитал код, который был простым RollBack() при ошибке с использованием IDisposable. Класс MiniTx ниже будет проверять флаг Dispose(), и если Commit звонить никогда не было, потом звонил Rollback на себя. Это добавило слой косвенности, что значительно облегчило понимание и поддержку вызывающего кода. Результат выглядел примерно так:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

Я также видел, что тайминг / логирование кода делают то же самое. В этом случае метод Dispose() остановил таймер и зарегистрировал, что блок вышел.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

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

Если вы хотите удалить прямо сейчас, используйте неуправляемую память.

Увидеть:

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

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

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

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

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

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

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

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

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

IDisposable хорошо для отписки от событий.

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

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

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

Ваш пример кода не является хорошим примером для 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'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.
    }
}

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

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

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

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

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

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

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

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

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

  1. Если созданный мной класс использует некоторые неуправляемые ресурсы, то это означает, что я должен также реализовать интерфейс IDisposable для очистки памяти.
  2. Очистите объекты, как только я закончу их использовать.
  3. В моем методе dispose я перебираю все IDisposable члены класса и вызываю Dispose.
  4. В моем методе Dispose вызовите GC.SuppressFinalize(this), чтобы уведомить сборщик мусора о том, что мой объект уже очищен. Я делаю это потому, что вызов GC - это дорогая операция.
  5. В качестве дополнительной меры предосторожности я пытаюсь сделать возможным вызов Dispose() несколько раз.
  6. Иногда я добавляю закрытый член _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;
        }
    }

Существуют дополнительные причины для реализации Dispose, например, чтобы освободить выделенную память, удалить элемент, добавленный в коллекцию, или сигнализировать о снятии полученной блокировки.Более

Я думаю, что люди путают ШАБЛОН IDisposable с основной целью IDisposable, которая должна была помочь очистить неуправляемые ресурсы. Мы все это знаем. Некоторые думают, что узор обладает какой-то магической силой, которая очищает память и высвобождает ресурсы. ШАБЛОН НЕ делает этого. Но использование шаблона с реализованными методами очищает память и освобождает ресурсы.

Шаблон представляет собой просто встроенный блок try{} finally{} . Больше ничего. Не меньше. Так что это значит? Вы можете создать блок кода, который позволит вам сделать что-то в конце без необходимости делать для этого дополнительный код. Он предоставляет блок CUSTOM , который вы можете использовать для сегментации кода и области действия.

Мой пример:

      //My way
using (var _ = new Metric("My Test"))
{
    DoSomething();  //You now know all work in your block is being timed.
}

//MS mockup from memory
var sw = new Stopwatch();
sw.Start();
DoSomething();  //something fails? I never get the elapsed time this way
sw.Stop();

Метрический класс

          public class Metric : IDisposable
    {
        private string _identifier;
        private DateTime _start;

        public Metric(string identifier)
        {
            _identifier = identifier;
            _start = DateTime.Now;
        }

        public void Dispose()
        {
            Console.WriteLine(_identifier + " - " + (DateTime.Now - _start).TotalMilliseconds)
        }
    }
Другие вопросы по тегам