Утилизировать, когда он называется?

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

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 самостоятельно (в основном то же самое). Могу ли я заставить пользователя использовать использование? Или я могу заставить вызов dispose быть вызванным?

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

Помещение di в null не вызывает Dispose. Деконструктор DOES работает, поэтому объект деконструируется, когда он выходит из Test()

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

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

Ответ 1

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

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

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

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

Ответ 2

Несколько важных моментов должны быть сделаны для решения вопроса ОП:

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

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

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

Завершение: Завершение - это способ .NET Framework для решения проблемы "изящной" очистки и освобождения ресурсов.

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

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

Ответ 3

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

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() приведет к удалению.

Ответ 4

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

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 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(...){}.

Ответ 5

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

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

Возможное решение: используйте или вызовите Уберите себя (в основном то же самое).

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

Ответ 6

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

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

Ответ 7

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

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

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