Вызовет ли сборщик мусора IDisposable.Распоряжаться за меня?

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

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

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

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

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

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

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

9 ответов


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

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

посмотреть http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx дополнительные сведения


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

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

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

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

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

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

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

~MyClass() { }

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

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


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

  • сборщик мусора никогда напрямую не выполнит метод Dispose для вас.
  • GC будет выполнить финализаторы, когда он чувствует, как он.
  • один общий шаблон, который используется для объектов, имеющих финализатор, должен вызвать метод, который по соглашению определяется как Dispose(bool disposing) passing 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();
 }
}

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


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

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

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

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

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


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

посмотреть этот статьи для обсуждения наилучшего способа справиться с этим.


нет, это не называется.

но это делает легким не забыть распорядиться вашими объектами. Просто используйте 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!");
    }

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


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

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

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