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


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

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

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

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

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

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

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

9 115

9 ответов:

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

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 (повторяя Gen 0), а затем, если все еще недостаточно, он выполняет развертку Gen 2 (повторяя 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 (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.