Будет ли сборщик мусора звонить IDisposable.Dispose для меня?

.NET IDisposable Pattern подразумевает, что если вы напишите финализатор и реализуете IDisposable, то ваш финализатор должен явно вызвать Dispose. Это логично, и это то, что я всегда делал в редких ситуациях, когда гарантируется финализатор.

Однако, что произойдет, если я просто сделаю это:

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}

и не выполнять финализатор или что-то еще. Будет ли фреймворк вызывать метод Dispose для меня?

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

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

  • Компилятор/фрейм делает другие "магические" вещи в зависимости от того, какие интерфейсы вы реализуете (например: foreach, методы расширения, сериализация на основе атрибутов и т.д.), поэтому имеет смысл, что это может быть "волшебным" тоже,

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

Ответ 1

Сборщик .Net Garbage Collector вызывает метод Object.Finalize объекта для сбора мусора. По по умолчанию это ничего и должно быть переопределено, если вы хотите освободить дополнительные ресурсы.

Dispose НЕ вызывается автоматически и должен быть вызван explicity, если ресурсы будут выпущены, например, в блоке 'using' или 'try finally'

см. http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx для получения дополнительной информации

Ответ 2

Я хочу подчеркнуть точку Брайана в его комментарии, потому что это важно.

Финализаторы не являются детерминированными деструкторами, как в С++. Как указывали другие, нет гарантии того, когда оно будет называться, и действительно, если у вас будет достаточно памяти, если это когда-нибудь будет называться.

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

Как вы можете или не можете знать, GC разбивается на поколения - генераторы 0, 1 и 2, плюс большая куча объектов. Split - свободный термин - вы получаете один блок памяти, но есть указатели на то, где начинаются и заканчиваются объекты Gen 0.

Процесс мышления состоит в том, что вы, вероятно, будете использовать множество объектов, которые будут недолговечными. Поэтому для GC необходимо легко и быстро добраться до объектов Gen 0. Поэтому, когда есть давление памяти, первое, что он делает, это коллекция Gen 0.

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

Это означает, что если вы сделаете что-то вроде этого:

~MyClass() { }

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

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

Ответ 3

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

  • Сборщик мусора никогда не будет напрямую выполнять метод Dispose для вас.
  • GC выполнит финализаторы, когда это будет похоже.
  • Один общий шаблон, который используется для объектов, у которых есть финализатор, - это вызов метода, который по определению определяется как Dispose (bool disposing), передающий false, чтобы указать, что вызов был выполнен из-за финализации, а не из явного Dispose звоните.
  • Это связано с тем, что при завершении объекта (они, возможно, уже завершены) небезопасно делать какие-либо предположения о других управляемых объектах.

class SomeObject : IDisposable {
 IntPtr _SomeNativeHandle;
 FileStream _SomeFileStream;

 // Something useful here

 ~ SomeObject() {
  Dispose(false);
 }

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

 protected virtual void Dispose(bool disposing) {
  if(disposing) {
   GC.SuppressFinalize(this);
   //Because the object was explicitly disposed, there will be no need to 
   //run the finalizer.  Suppressing it reduces pressure on the GC

   //The managed reference to an IDisposable is disposed only if the 
   _SomeFileStream.Dispose();
  }

  //Regardless, clean up the native handle ourselves.  Because it is simple a member
  // of the current instance, the GC can't have done anything to it, 
  // and this is the onlyplace to safely clean up

  if(IntPtr.Zero != _SomeNativeHandle) {
   NativeMethods.CloseHandle(_SomeNativeHandle);
   _SomeNativeHandle = IntPtr.Zero;
  }
 }
}

Это простая версия, но есть много нюансов, которые могут направить вас на этот шаблон.

  • Контракт на IDisposable.Dispose указывает, что он должен быть безопасным для вызова несколько раз (вызов Dispose на объект, который уже был удален, ничего не должен делать)
  • Очень сложно правильно управлять иерархией наследования одноразовых объектов, особенно если разные слои вводят новые одноразовые и неуправляемые ресурсы. В шаблоне выше Dispose (bool) является виртуальным, чтобы его можно было переопределить, чтобы его можно было управлять, но я считаю, что он подвержен ошибкам.

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

Простое определение SafeHandle делает это тривиальным:


private class SomeSafeHandle
 : SafeHandleZeroOrMinusOneIsInvalid {
 public SomeSafeHandle()
  : base(true)
  { }

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}

Позволяет упростить содержащийся тип:


class SomeObject : IDisposable {
 SomeSafeHandle _SomeSafeHandle;
 FileStream _SomeFileStream;
 // Something useful here
 public virtual void Dispose() {
  _SomeSafeHandle.Dispose();
  _SomeFileStream.Dispose();
 }
}

Ответ 4

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


EDIT: я ушел и протестировал, просто чтобы убедиться:

class Program
{
    static void Main(string[] args)
    {
        Fred f = new Fred();
        f = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Fred gone, and he not coming back...");
        Console.ReadLine();
    }
}

class Fred : IDisposable
{
    ~Fred()
    {
        Console.WriteLine("Being finalized");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Being Disposed");
    }
}

Ответ 5

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

ОДНАКО. Следующая сборка мусора, вместо того, чтобы собираться, объект войдет в очередь финализации, все будет собрано, а затем вызывается финализатор. Следующая коллекция после этого будет освобождена.

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

Ответ 6

В GC будет не вызов. Он может вызывать ваш финализатор, но даже это не гарантируется при любых обстоятельствах.

Смотрите статью, чтобы обсудить лучший способ справиться с этим.

Ответ 7

Нет, он не вызван.

Но это позволяет легко забыть об утилизации объектов. Просто используйте ключевое слово using.

Я сделал следующий тест для этого:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        foo = null;
        Console.WriteLine("foo is null");
        GC.Collect();
        Console.WriteLine("GC Called");
        Console.ReadLine();
    }
}

class Foo : IDisposable
{
    public void Dispose()
    {

        Console.WriteLine("Disposed!");
    }

Ответ 8

Документация на IDisposable дает довольно четкое и подробное объяснение поведения, а также пример кода. GC не будет вызывать метод Dispose() на интерфейсе, но он вызовет финализатор для вашего объекта.

Ответ 9

Шаблон IDisposable был создан в первую очередь для того, чтобы его вызывал разработчик, если у вас есть объект, реализующий IDispose, разработчик должен либо реализовать ключевое слово using в контексте объекта, либо напрямую обратиться к методу Dispose.

Сбой безопасности для шаблона заключается в реализации финализатора, вызывающего метод Dispose(). Если вы этого не сделаете, вы можете создать утечку памяти, то есть: если вы создадите некоторую COM-оболочку и никогда не вызываете System.Runtime.Interop.Marshall.ReleaseComObject(comObject) (который будет помещен в метод Dispose).

В clr нет волшебства, чтобы вызывать методы Dispose автоматически, кроме отслеживания объектов, которые содержат финализаторы, и хранить их в таблице Finalizer GC и вызывать их, когда какая-то эвристика эмулирования запускается GC.