пример c# Правильное использование интерфейса IDisposable




9 Answers

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

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}
шаблон dispose c#

Я знаю из чтения документации MSDN, что «основное» использование интерфейса IDisposable - очистка неуправляемых ресурсов.

Для меня «неуправляемый» означает такие вещи, как соединения с базой данных, сокеты, дескрипторы окон и т. Д. Но я видел код, в котором метод Dispose() реализован для освобождения управляемых ресурсов, что кажется излишним для меня, поскольку сборщик мусора должен позаботьтесь об этом для вас.

Например:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Мой вопрос заключается в том, делает ли сборщик мусора свободной памятью, используемой MyCollection быстрее, чем обычно?

edit : Пока люди опубликовали несколько хороших примеров использования IDisposable для очистки неуправляемых ресурсов, таких как соединения с базой данных и растровые изображения. Но предположим, что _theList в приведенном выше коде содержал миллион строк, и вы хотели освободить эту память сейчас , а не ждать сборщика мусора. Выполнил бы этот код?




Не должно быть никаких дальнейших вызовов методов объекта после вызова Dispose (хотя объект должен терпеть дальнейшие вызовы Dispose). Поэтому пример в вопросе глупо. Если Dispose вызывается, то сам объект может быть отброшен. Таким образом, пользователь должен просто отбросить все ссылки на весь этот объект (установить их в null), и все связанные с ним объекты будут автоматически очищены.

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

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

Но - и это ключ - они могут быть любой подходящей парой функций. Один строит состояние, другой срывает его. Если государство было построено, но не снесено, то существует экземпляр ресурса. Вам необходимо организовать разрывы в нужное время - ресурс не управляется CLR. Единственным автоматически управляемым типом ресурса является память. Существует два вида: GC и стек. Типы значений управляются стеком (или путем запуска езды по ссылочным типам), а типы ссылок управляются GC.

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

Посмотрите на пример в вопросе правосудия. Изменения в отступе файла журнала должны быть полностью вложенными, или все идет не так. Также они вряд ли будут потокобезопасными.

Можно взять автомобиль с сборщиком мусора, чтобы очистить ваши неуправляемые ресурсы. Но только если функции изменения состояния являются потокобезопасными, а два состояния могут иметь периоды жизни, которые перекрываются каким-либо образом. Поэтому пример юстиции ресурса НЕ должен иметь финализатор! Это никому не помогло.

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

Затем вам нужно использовать оператор using чтобы иметь возможность гарантировать, что Dispose вызывается. Это по существу похоже на то, что вы едете со стеком (так как финализатор относится к GC, using стек).

Недостающая часть заключается в том, что вам нужно вручную записать Dispose и заставить ее вызывать свои поля и ваш базовый класс. Программистам C ++ / CLI этого не нужно. В большинстве случаев компилятор записывает их для них.

Существует альтернатива, которую я предпочитаю для состояний, которые идеально подходят и не являются потокобезопасными (помимо всего прочего, избегая IDisposable spares, вы сталкиваетесь с проблемой наличия аргумента с тем, кто не может удержаться от добавления финализатора для каждого класса, который реализует IDisposable) ,

Вместо написания класса вы пишете функцию. Функция принимает делегата для обратного вызова:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

И тогда простой пример:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

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

Этот метод менее полезен, если ресурс является видом, который может иметь перекрывающиеся сроки жизни, потому что тогда вы хотите построить ресурс A, затем ресурс B, а затем уничтожить ресурс A, а затем уничтожить ресурс B. Вы не можете этого сделать если вы заставили пользователя идеально вложить это гнездо. Но тогда вам нужно использовать IDisposable (но все еще без финализатора, если только вы не реализовали технологию потоков, которая не является бесплатной).




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

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

EDIT В ответ на комментарий Скотта:

Единственный раз, когда влияют показатели производительности GC, - это когда вызывается [sic] GC.Collect ()

Концептуально, GC поддерживает представление об объектном графике объекта и все ссылки на него из фреймов стека потоков. Эта куча может быть довольно большой и охватывать многие страницы памяти. В качестве оптимизации GC анализирует страницы, которые вряд ли будут меняться очень часто, чтобы избежать повторного сканирования страницы без необходимости. GC получает уведомление от ядра, когда данные на странице меняются, поэтому он знает, что страница загрязнена и требует повторного сканирования. Если коллекция находится в Gen0, то, вероятно, другие вещи на странице тоже меняются, но это менее вероятно в Gen1 и Gen2. Анекдотически эти крючки недоступны в Mac OS X для команды, которая портировала GC на Mac, чтобы получить подключаемый модуль Silverlight, работающий на этой платформе.

Еще один момент против ненужной утилизации ресурсов: представьте ситуацию, когда процесс разгружается. Представьте также, что этот процесс работает некоторое время. Скорее всего, многие страницы памяти этого процесса были заменены на диск. По крайней мере, они больше не находятся в кешках L1 или L2. В такой ситуации нет разницы в том, что приложение, которое выгружает, заменяет все эти данные и страницы кода обратно в память, чтобы «освободить» ресурсы, которые в любом случае будут освобождены операционной системой, когда процесс завершится. Это относится к управляемым и даже к неуправляемым ресурсам. Должны быть утилизированы только ресурсы, поддерживающие не-фоновые потоки, иначе процесс останется в живых.

Теперь во время обычного выполнения есть эфемерные ресурсы, которые необходимо очистить правильно (поскольку @fezmonkey указывает соединения с базой данных, сокеты, дескрипторы окон ), чтобы избежать неуправляемых утечек памяти.Это те вещи, которые нужно уничтожить. Если вы создаете какой-то класс, которому принадлежит поток (и по собственному значению я подразумеваю, что он его создал, и поэтому он отвечает за то, чтобы он остановился, по крайней мере, по моему стилю кодирования), тогда этот класс, скорее всего, должен реализовать IDisposableи снести поток во время Dispose.

.NET framework использует IDisposableинтерфейс как сигнал, даже предупреждающий разработчикам о том, что этот класс должен быть удален. Я не могу думать о каких-либо типах в рамках реализации IDisposable(исключая явные реализации интерфейса), где удаление необязательно.







В приведенном выше примере он по-прежнему не «освобождает память сейчас». Вся память собрана мусором, но это может позволить сбор памяти в более раннем generation . Конечно, вам нужно будет выполнить некоторые тесты.

Руководство по разработке рамок - это руководство, а не правила. Они расскажут вам, для чего в первую очередь используется интерфейс, когда его использовать, как его использовать и когда его не использовать.

Я однажды прочитал код, который был простым откатом RollBack () при использовании IDisposable. Класс MiniTx, указанный ниже, будет проверять флаг на Dispose (), и если Commitвызов никогда не произошел, он будет сам называть Rollback. Он добавил слой косвенности, делающий код вызова намного понятным и поддерживающим. Результат выглядел примерно так:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

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

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

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




Помимо своего основного применения в качестве способа контролировать срок службы в системных ресурсах (полностью покрыта удивительным ответом Яна , престижность!), То IDisposable / с использованием комбы также может быть использован для определения области изменения состояния (критических) глобальных ресурсов : консоли , то потоки , то процесс , любой глобальный объект как экземпляр приложения .

Я написал статью об этом шаблоне: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

Он иллюстрирует, как вы можете защитить некоторое часто используемое глобальное состояние в многоразовом и читабельном виде: цвета консоли , текущая культура потока , свойства объекта приложения Excel ...




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

Вместо этого нужно понимать, что общего у всех управляемых ресурсов: все они влекут за собой объект, требующий от какого-то внешнего «дела» сделать что-то от его имени, в ущерб каким-то другим «вещам», а другой субъект соглашается сделать это до тех пор, пока дальнейшего уведомления. Если объект должен был быть оставлен и исчезнут без следа, ничто не могло бы сказать, что вне «вещи» ему больше не нужно было изменять свое поведение от имени объекта, который больше не существовал; следовательно, полезность «вещи» будет постоянно уменьшаться.

Таким образом, неуправляемый ресурс представляет собой соглашение какой-либо внешней «вещи», чтобы изменить свое поведение от имени объекта, что бесполезно ухудшало бы полезность этой внешней «вещи», если бы объект был оставлен и прекратил свое существование. Управляемый ресурс - это объект, который является бенефициаром такого соглашения, но который зарегистрировался для получения уведомления, если он оставлен, и который будет использовать такое уведомление, чтобы привести свои дела в порядок до его уничтожения.




Первое из определения. Для меня неуправляемый ресурс означает некоторый класс, который реализует интерфейс IDisposable или что-то созданное с использованием вызовов в dll. GC не знает, как бороться с такими объектами. Если класс имеет, например, только типы значений, то я не рассматриваю этот класс как класс с неуправляемыми ресурсами. Для моего кода я следую следующим практикам:

  1. Если созданный мной класс использует некоторые неуправляемые ресурсы, это значит, что я должен также реализовать интерфейс IDisposable для очистки памяти.
  2. Очистите объекты, как только я закончу их использование.
  3. В моем методе dispose я перебираю все IDisposable членов класса и вызываю Dispose.
  4. В моем методе метода Dispose GC.SuppressFinalize (this), чтобы уведомить сборщика мусора о том, что мой объект уже очищен. Я делаю это, потому что вызов GC - это дорогостоящая операция.
  5. В качестве дополнительной меры предосторожности я пытаюсь сделать возможным вызов Dispose () несколько раз.
  6. Иногда я добавляю закрытый член _disposed и проверяет вызовы методов, если объект был очищен. И если он был очищен, сгенерируйте ObjectDisposedException
    Следующий шаблон демонстрирует то, что я описал в словах как образец кода:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

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

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }



Ваш образец кода не является хорошим примером IDisposableиспользования. Обычно для очистки словаря не следует обращаться к Disposeметоду. Элементы словаря будут очищены и удалены, когда они выйдут из сферы действия. IDisposableдля освобождения некоторых модулей памяти / обработчиков, которые не будут освобождены / освобождены даже после того, как они будут недоступны.

В следующем примере показан хороший пример для шаблона IDisposable с некоторым кодом и комментариями.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // 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. 
        protected virtual 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.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

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

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // 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.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}



Related