Точка 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 МБ системы . Рисунок. Растровое изображение (т.е. е. . 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 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.
}
Проблема в том, что вы понятия не имеете, когда сборщик мусора дойдет до завершения вашего объекта. Ваши неуправляемые, ненужные, неиспользуемые собственные ресурсы будут работать до тех пор, пока сборщик мусора в конечном итоге не запустится . Затем он вызовет ваш метод финализатора; очистка неуправляемых ресурсов. Документация Объект. Завершить баллов это:
Точное время выполнения финализатора не определено. Чтобы обеспечить детерминированное освобождение ресурсов для экземпляров вашего класса, реализуйте метод Close Close или предоставьте реализацию 285358709 IDisposable.Dispose
.
Это достоинство использования Dispose
для очистки неуправляемых ресурсов; Вы узнаете и контролируете, когда неуправляемые ресурсы очищаются. Их уничтожение "детерминировано" .
Чтобы ответить на ваш первоначальный вопрос: почему бы не освободить память сейчас, а не тогда, когда GC решит это сделать? У меня есть программное обеспечение для распознавания лиц, которому нужно , чтобы избавиться от 530 МБ внутренних изображений. теперь , так как они больше не нужны. Когда мы этого не делаем: машина переходит в режим обмена.
Чтение бонусов
Для тех, кому нравится стиль этого ответа (объясняющий , почему , поэтому , как становится очевидным), я предлагаю вам прочитать Главу 1 «Основного COM» Дона Бокса:
На 35 страницах он объясняет проблемы использования бинарных объектов и изобретает COM на ваших глазах. Как только вы поймете, почему 3230269583 почему COM, остальные 300 страниц очевидны, и просто подробно описывают реализацию Microsoft.
Я думаю, что каждый программист, который когда-либо имел дело с объектами или COM, должен, по крайней мере, прочитать первую главу. Это лучшее объяснение чего-либо.
Дополнительное чтение бонусов
Когда все, что вы знаете, не так от Eric Lippert
Поэтому очень трудно действительно написать правильный финализатор, и лучший совет, который я могу вам дать, это не пытаться .