Правильное использование интерфейса IDisposable - PullRequest
1521 голосов
/ 11 февраля 2009

Из чтения документации 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 в приведенном выше коде содержит миллион строк, и вы хотели освободить эту память сейчас , а не ждать сборщика мусора. Будет ли код, приведенный выше, выполнить это?

Ответы [ 19 ]

2447 голосов
/ 11 февраля 2009

Точка 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);
}

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


Что если ваш объект выделил 250 МБ 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(). Вы пишете метод, который выглядит как a 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, не забудьте вызвать их base Метод 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(), мы имеем:

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

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

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

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

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

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

57 голосов
/ 11 февраля 2009

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.");
        }
    }
}
37 голосов
/ 11 февраля 2009

Целью шаблона 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 собирать / освобождать объект; он просто предоставляет средства для более детерминированной очистки используемых ресурсов и сообщает GC, что эта очистка уже выполнена.

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

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

17 голосов
/ 11 февраля 2009

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

14 голосов
/ 11 февраля 2009

Сценарии, которые я использую 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
}
11 голосов
/ 11 февраля 2009

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

11 голосов
/ 11 февраля 2009

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

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

См:

6 голосов
/ 12 февраля 2009

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


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

Однажды я прочитал код, который был простым 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 для создания более чистого кода.

6 голосов
/ 12 февраля 2009

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

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

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

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

...