Утилизировать, когда это называется? - PullRequest
28 голосов
/ 20 мая 2010

Рассмотрим следующий код:

namespace DisposeTest
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Calling Test");

            Test();

            Console.WriteLine("Call to Test done");
        }

        static void Test()
        {
            DisposeImplementation di = new DisposeImplementation();
        }
    }

    internal class DisposeImplementation : IDisposable
    {
        ~DisposeImplementation()
        {
            Console.WriteLine("~ in DisposeImplementation instance called");
        }
        public void Dispose()
        {
            Console.WriteLine("Dispose in DisposeImplementation instance called");
        }
    }
}

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

Возможное решение: используйте using или позвоните в Dispose себя (в основном то же). Могу ли я заставить пользователя использовать использование? Или я могу заставить называться распоряжением?

Вызов GC.Collect(); после Test(); тоже не работает.

Установка di на null также не вызывает утилизацию. Деконструктор работает, поэтому объект получает деконструкцию при выходе Test()

Хорошо, ребята, теперь понятно!

Спасибо всем за ответы! Я добавлю предупреждение в комментарии!

Ответы [ 7 ]

46 голосов
/ 24 февраля 2012

Для решения вопроса ОП необходимо сделать пару важных замечаний:

  1. .NET GC недетерминирован (то есть вы никогда не знаете и не должны зависеть от того, когда это произойдет)
  2. Утилизация никогда не вызывается .NET Framework; Вы должны вызвать его вручную - желательно, поместив его создание в блок using().
  3. Явная установка одноразового объекта в null без вызова Dispose () для него - плохая вещь. Что происходит, так это то, что вы явно устанавливаете для объектов «корневая ссылка» значение null. Это фактически означает, что вы не можете вызвать Dispose позже И, что более важно, он отправляет объект в очередь завершения GC для завершения. Причинение Финализации плохой практикой программирования следует избегать любой ценой.

Финалайзер: Некоторые разработчики называют его деструктором. И фактически он даже называется деструктором в спецификации языка C # 4.0 (раздел 1.6.7.6) и в предыдущих версиях текущей спецификации ECMA-334 . К счастью, 4-е издание (июнь 2006 г.) правильно определяет финализаторы в разделе 8.7.9 и пытается устранить путаницу между ними в разделе 17.12. Следует отметить, что существуют важные внутренние различия (не нужно вдаваться в эти ужасные подробности) между тем, что традиционно называется деструктором и деструктором / финализатором в .NET Framework.

  1. Если присутствует финализатор, он будет вызываться .NET Framework тогда и только тогда, когда GC.SuppressFinalize() не вызывается.
  2. Вы НИКОГДА не должны явно вызывать финализатор. К счастью, C # явно не допустит этого (я не знаю о других языках); хотя это может быть вызвано вызовом GC.Collect(2) для второго поколения GC.

Доработка: Финализация - это способ .NET Framework справиться с «изящной» очисткой и освобождением ресурсов.

  1. Это происходит только при наличии объектов в очереди на финализацию.
  2. Это происходит только тогда, когда сборка мусора происходит для Gen2 (что составляет приблизительно 1 в каждых 100 коллекциях для хорошо написанного приложения .NET).
  3. Вплоть до .NET 4 включительно существует один поток Финализации. Если этот поток заблокирован по какой-либо причине, ваше приложение облажалось.
  4. Написание правильного и безопасного кода финализации нетривиально, и ошибки могут быть сделаны довольно легко (то есть, случайно допущены исключения из финализатора, допускаются зависимости от других объектов, которые уже могут быть завершены и т. Д.)

Хотя это, безусловно, больше информации, которую вы запрашивали, она дает представление о том, как все работает и почему они работают так, как работают. Некоторые люди утверждают, что им не нужно беспокоиться об управлении памятью и ресурсами в .NET, но это не меняет того факта, что это нужно делать, и я не думаю, что это исчезнет в ближайшем будущем.

20 голосов
/ 20 мая 2010

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

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

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

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

13 голосов
/ 20 мая 2010

Все ответы (более или менее) правильны, вот пример:

static void Test()
{
    using (DisposeImplementation di = new DisposeImplementation())
    {
        // Do stuff with di
    }
}

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

Вы можете добавить финализатор, который обрабатывает удаление ресурсов в случае, если кто-то «забудет» использовать интерфейс IDisposable:

public class DisposeImplementation : IDisposable
{    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    }

    ~DisposeImplementation()
    {
        Dispose(false);
    }
}

См. этот вопрос для получения дополнительной информации. Однако это просто компенсирует людей, которые неправильно используют ваш класс :) Я предлагаю вам добавить большой толстый Debug.Fail() вызов в Finalizer, чтобы предупредить разработчика об их ошибке.

Если вы решите реализовать шаблон, вы увидите, что GC.Collect() вызовет удаление.

7 голосов
/ 20 мая 2010

Используйте это как шаблон / шаблон для ваших классов

public class MyClass : IDisposable
{
    private bool disposed = false;

    // Implement IDisposable.
    // Do not make this method virtual.
    // A derived class should not be able to override this method.
    public void Dispose()
    {
        Dispose(true);
        // This object will be cleaned up by the Dispose method.
        // Therefore, you should call GC.SupressFinalize to
        // take this object off the finalization queue
        // and prevent finalization code for this object
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    // Dispose(bool disposing) executes in two distinct scenarios.
    // If disposing equals true, the method has been called directly
    // or indirectly by a user's code. Managed and unmanaged resources
    // can be disposed.
    // If disposing equals false, the method has been called by the
    // runtime from inside the finalizer and you should not reference
    // other objects. Only unmanaged resources can be disposed.
    private void Dispose(bool disposing)
    {
        // Check to see if Dispose has already been called.
        if (!this.disposed)
        {
            // If disposing equals true, dispose all managed
            // and unmanaged resources.
            if (disposing)
            {
                // Dispose managed resources.                
                ......
            }

            // Call the appropriate methods to clean up
            // unmanaged resources here.
            // If disposing is false,
            // only the following code is executed.
            ...........................

            // Note disposing has been done.
            disposed = true;
        }
    }

    // Use C# destructor syntax for finalization code.
    // This destructor will run only if the Dispose method
    // does not get called.
    // It gives your base class the opportunity to finalize.
    // Do not provide destructors in types derived from this class.
    ~MyClass()
    {
        // Do not re-create Dispose clean-up code here.
        // Calling Dispose(false) is optimal in terms of
        // readability and maintainability.
        Dispose(false);
    }
}

И, конечно, как уже упоминалось, не забывайте про блок using(...){}.

2 голосов
/ 20 мая 2010

Вам придется вызывать Dispose явно или обернуть объект в оператор using. Пример:

using (var di = new DisposeImplementation())
{
}

Возможное решение: использовать с помощью или позвонить Избавьтесь от себя (в основном то же самое).

Использование using аналогично вызову Dispose внутри блока finally.

1 голос
/ 20 мая 2010

Утилизация не вызывается автоматически. Вы должны использовать предложение using для переноса использования или вызывать его вручную.

См. http://msdn.microsoft.com/en-us/library/aa664736%28VS.71%29.aspx

И просто чтобы вытеснить другую идею, которая может у вас возникнуть: вы не можете вызвать dispose от деструктора ... Я пытался это сделать некоторое время назад в проекте.

1 голос
/ 20 мая 2010

Вы должны избавиться от него самостоятельно, либо вызывая метод Dispose, либо используя using. Помните, это не деконструктор!

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

...