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

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

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

Например:

public class MyCollection : IDisposable
{
    private List _theList = new List();
    private Dictionary _theDict = new Dictionary();

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

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

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

вопрос задан 11.02.2009
cwick
10591 репутация

18 ответов


  • 2328 рейтинг

    Точка Dispose - для освобождения неуправляемых ресурсов. Это нужно сделать в какой-то момент, иначе они никогда не будут очищены. Сборщик мусора не знает , как вызывать DeleteHandle() для переменной типа IntPtr, он не знает , нужно ли или нет, вызывать DeleteHandle().

    Примечание : Что такое неуправляемый ресурс ? Если вы нашли это в Microsoft. NET Framework: это управляется. Если вы сами ковырялись в MSDN, это неуправляемо. Все, что вы использовали для вызовов P / Invoke, выходит из приятного и удобного мира всего, что вам доступно в. NET Framework неуправляем - и вы не несете ответственности за его очистку.

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

    public void Cleanup()
    
    public void Shutdown()
    

    Но вместо этого есть стандартизированное имя для этого метода:

    public void Dispose()
    

    Был даже создан интерфейс, IDisposable, который имеет только один метод:

    public interface IDisposable
    {
       void Dispose()
    }
    

    Итак, вы заставляете ваш объект предоставлять интерфейс IDisposable, и таким образом вы обещаете, что написали этот единственный метод для очистки ваших неуправляемых ресурсов:

    public void Dispose()
    {
       Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
    }
    

    И все готово. За исключением того, что вы можете сделать лучше.


    Что если ваш объект выделил 250 МБ системы . Рисунок. Растровое изображение (т.е. е. . NET управляет классом Bitmap) как своего рода буфер кадра? Конечно, это удалось. NET объект, и сборщик мусора освободит его. Но вы действительно хотите оставить 250 МБ памяти, просто сидя там - в ожидании сборщика мусора, чтобы в конечном итоге пришел и освободить его? Что если есть открытое соединение с базой данных ? Конечно, мы не хотим, чтобы это соединение оставалось открытым, ожидая, пока GC завершит объект.

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

    Так что теперь мы будем:

    • избавиться от неуправляемых ресурсов (потому что мы должны), а
    • избавиться от управляемых ресурсов (потому что мы хотим быть полезными)

    Итак, давайте обновим наш метод Dispose(), чтобы избавиться от этих управляемых объектов:

    public void Dispose()
    {
       //Free unmanaged resources
       Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
    
       //Free managed resources too
       if (this.databaseConnection != null)
       {
          this.databaseConnection.Dispose();
          this.databaseConnection = null;
       }
       if (this.frameBufferImage != null)
       {
          this.frameBufferImage.Dispose();
          this.frameBufferImage = null;
       }
    }
    

    И все хорошо, за исключением того, что вы можете сделать лучше !


    Что, если человек забыл , чтобы позвонить Dispose() на ваш объект? Тогда они потеряли бы неуправляемых ресурсов!

    Примечание: Они не будут пропускать управляемых ресурсов , поскольку в конечном итоге сборщик мусора будет работать в фоновом потоке и освобождать память, связанную с любыми неиспользованными объектами. Это будет включать ваш объект и любые управляемые объекты, которые вы используете (например, г. Bitmap и DbConnection).

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

    Примечание: Сборщик мусора в конечном итоге освободит все управляемые объекты. Когда это происходит, он вызывает Finalize метод на объекте. GC не знает, или забота, о ваш Утилизировать метод. Это было просто имя, которое мы выбрали для метод, который мы вызываем, когда мы хотим получить избавиться от неуправляемых вещей.

    Уничтожение нашего объекта сборщиком мусора - это идеальное время для освобождения этих надоедливых неуправляемых ресурсов. Мы делаем это путем переопределения метода Finalize().

    Примечание: В C # вы явно не переопределяете метод Finalize(). Вы пишете метод, который выглядит как C ++ деструктор , и компилятор считает, что это ваша реализация метода Finalize():

    ~MyObject()
    {
        //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
        Dispose(); //<--Warning: subtle bug! Keep reading!
    }
    

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

    public void Dispose()
    {
       //Free unmanaged resources
       Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
    
       //Free managed resources too
       if (this.databaseConnection != null)
       {
          this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
          this.databaseConnection = null;
       }
       if (this.frameBufferImage != null)
       {
          this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
          this.frameBufferImage = null;
       }
    }
    

    Итак, вам нужен способ для Finalize() сообщить Dispose(), что не должен касаться каких-либо управляемых ресурсов (поскольку они могут больше не находиться там ), при этом все еще освобождая неуправляемые ресурсы.

    Стандартный шаблон для этого состоит в том, чтобы Finalize() и Dispose() оба вызывали третий (! ) метод; где вы передаете логическое высказывание, если вы вызываете его с Dispose() (в отличие от Finalize()), то есть безопасно освобождать управляемые ресурсы.

    Этот внутренний внутренний метод может дать какое-то произвольное имя, например «CoreDispose» или «MyInternalDispose», но традиционно его называют Dispose(Boolean):

    protected void Dispose(Boolean disposing)
    

    Но более полезное имя параметра может быть:

    protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
    {
       //Free unmanaged resources
       Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
    
       //Free managed resources too, but only if I'm being called from Dispose
       //(If I'm being called from Finalize then the objects might not exist
       //anymore
       if (itIsSafeToAlsoFreeManagedObjects)  
       {    
          if (this.databaseConnection != null)
          {
             this.databaseConnection.Dispose();
             this.databaseConnection = null;
          }
          if (this.frameBufferImage != null)
          {
             this.frameBufferImage.Dispose();
             this.frameBufferImage = null;
          }
       }
    }
    

    И вы измените свою реализацию метода IDisposable.Dispose() на:

    public void Dispose()
    {
       Dispose(true); //I am calling you from Dispose, it's safe
    }
    

    и ваш финализатор:

    ~MyObject()
    {
       Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
    }
    

    Примечание : Если ваш объект происходит от объекта, который реализует Dispose, то не забудьте вызвать его базовый Метод Dispose при переопределении Dispose:

    public Dispose()
    {
        try
        {
            Dispose(true); //true: safe to free managed resources
        }
        finally
        {
            base.Dispose();
        }
    }
    

    И все хорошо, за исключением того, что вы можете сделать лучше !


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

    Мало того, что это расточительно, но если ваш объект имеет ненужные ссылки на объекты, которые вы уже удалили из последнего вызова в Dispose(), вы попытаетесь утилизировать их снова!

    В моем коде вы заметите, что я осторожно удалял ссылки на объекты, которые я выбрал, поэтому я не пытаюсь вызвать Dispose для ссылки на ненужный объект. Но это не остановило незаметную ошибку.

    Когда пользователь вызывает Dispose(): дескриптор CursorFileBitmapIconServiceHandle уничтожается. Позже, когда запустится сборщик мусора, он снова попытается уничтожить ту же ручку.

    protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
    {
       //Free unmanaged resources
       Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
       ...
    }
    

    Способ, которым вы исправляете это, говорит сборщику мусора, что ему не нужно беспокоиться о завершении объекта - его ресурсы уже очищены, и больше не требуется никакой работы. Это можно сделать, вызвав GC.SuppressFinalize() в методе Dispose():

    public void Dispose()
    {
       Dispose(true); //I am calling you from Dispose, it's safe
       GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
    }
    

    Теперь, когда пользователь позвонил Dispose(), мы имеем:

    • освобожденных неуправляемых ресурсов
    • освобожденных управляемых ресурсов

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

    Не могу ли я использовать Finalize для очистки неуправляемых ресурсов?

    Документация для Object.Finalize гласит:

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

    Но документация MSDN также говорит, для IDisposable.Dispose :

    Выполняет определяемые приложением задачи, связанные с освобождением, освобождением или сбросом неуправляемых ресурсов.

    Так что же это? В каком месте я могу убрать неуправляемые ресурсы? Ответ:

    Это ваш выбор! Но выберите Dispose.

    Вы, конечно, можете поместить свою неуправляемую очистку в финализатор:

    ~MyObject()
    {
       //Free unmanaged resources
       Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
    
       //A C# destructor automatically calls the destructor of its base class.
    }
    

    Проблема в том, что вы понятия не имеете, когда сборщик мусора дойдет до завершения вашего объекта. Ваши неуправляемые, ненужные, неиспользуемые собственные ресурсы будут работать до тех пор, пока сборщик мусора в конечном итоге не запустится . Затем он вызовет ваш метод финализатора; очистка неуправляемых ресурсов. Документация Объект. Завершить баллов это:

    Точное время выполнения финализатора не определено. Чтобы обеспечить детерминированное освобождение ресурсов для экземпляров вашего класса, реализуйте метод Close Close или предоставьте реализацию 285358709 IDisposable.Dispose.

    Это достоинство использования Dispose для очистки неуправляемых ресурсов; Вы узнаете и контролируете, когда неуправляемые ресурсы очищаются. Их уничтожение "детерминировано" .


    Чтобы ответить на ваш первоначальный вопрос: почему бы не освободить память сейчас, а не тогда, когда GC решит это сделать? У меня есть программное обеспечение для распознавания лиц, которому нужно , чтобы избавиться от 530 МБ внутренних изображений. теперь , так как они больше не нужны. Когда мы этого не делаем: машина переходит в режим обмена.

    Чтение бонусов

    Для тех, кому нравится стиль этого ответа (объясняющий , почему , поэтому , как становится очевидным), я предлагаю вам прочитать Главу 1 «Основного COM» Дона Бокса:

    На 35 страницах он объясняет проблемы использования бинарных объектов и изобретает COM на ваших глазах. Как только вы поймете, почему 3230269583 почему COM, остальные 300 страниц очевидны, и просто подробно описывают реализацию Microsoft.

    Я думаю, что каждый программист, который когда-либо имел дело с объектами или COM, должен, по крайней мере, прочитать первую главу. Это лучшее объяснение чего-либо.

    Дополнительное чтение бонусов

    Когда все, что вы знаете, не так от Eric Lippert

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

    ответ дан Ian Boyd, с репутацией 114699, 11.02.2009
  • 55 рейтинг

    IDisposable часто используется для использования оператора 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.");
            }
        }
    }
    
    ответ дан yfeldblum, с репутацией 57916, 11.02.2009
  • 37 рейтинг

    Цель шаблона Dispose - предоставить механизм для очистки как управляемых, так и неуправляемых ресурсов, и когда это произойдет, зависит от того, как вызывается метод Dispose. В вашем примере использование Dispose на самом деле ничего не делает для удаления, поскольку очистка списка не влияет на удаление этой коллекции. Аналогично, вызовы для установки переменных на ноль также не влияют на сборщик мусора.

    Вы можете взглянуть на эту статью для более подробной информации о том, как реализовать шаблон Dispose, но в основном это выглядит так:

    public class SimpleCleanup : IDisposable
    {
        // some fields that require cleanup
        private SafeHandle handle;
        private bool disposed = false; // to detect redundant calls
    
        public SimpleCleanup()
        {
            this.handle = /*...*/;
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    // Dispose managed resources.
                    if (handle != null)
                    {
                        handle.Dispose();
                    }
                }
    
                // Dispose unmanaged managed resources.
    
                disposed = true;
            }
        }
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
    

    Наиболее важным здесь методом является Dispose (bool), который на самом деле работает при двух разных обстоятельствах:

    • утилизировать == true: метод был вызван прямо или косвенно кодом пользователя. Управляемые и неуправляемые ресурсы могут быть утилизированы.
    • утилита == false: метод был вызван средой выполнения из финализатора, и вы не должны ссылаться на другие объекты. Только неуправляемые ресурсы могут быть утилизированы.

    Проблема с тем, чтобы просто позволить GC позаботиться о проведении очистки, заключается в том, что у вас нет реального контроля над тем, когда GC будет запускать цикл сбора (вы можете вызвать GC. Collect (), но вы не должны), поэтому ресурсы могут оставаться дольше, чем нужно. Помните, что вызов Dispose () на самом деле не вызывает цикл сбора или каким-либо образом заставляет GC собирать / освобождать объект; он просто предоставляет средства для более детерминированной очистки используемых ресурсов и сообщает GC, что эта очистка уже выполнена.

    Весь смысл IDisposable и шаблона dispose не заключается в немедленном освобождении памяти. Единственный раз, когда вызов Dispose даже может сразу же освободить память, это когда он обрабатывает сценарий == false и манипулирует неуправляемыми ресурсами. Для управляемого кода память фактически не будет возвращена до тех пор, пока GC не выполнит цикл сбора, который вы действительно не можете контролировать (кроме вызова GC. Collect (), о котором я уже упоминал, не очень хорошая идея).

    Ваш сценарий не является действительным, так как строки в. NET не использует никаких неуправляемых ресурсов и не реализует IDisposable, нет способа заставить их «очиститься». "

    ответ дан Scott Dorman, с репутацией 34290, 11.02.2009
  • 17 рейтинг

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

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

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

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

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

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

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

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

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

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

    Существует альтернатива, которую я предпочитаю для состояний, которые отлично встраиваются и не являются поточно-ориентированными (кроме всего прочего, избегание IDisposable избавляет вас от проблемы с кем-то, кто не может сопротивляться добавлению финализатора в каждый класс, реализующий 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 (но все еще без финализатора, если вы не реализовали безопасность потоков, что не является бесплатным).

    ответ дан Daniel Earwicker, с репутацией 92262, 11.02.2009
  • 13 рейтинг

    Сценарии, которые я использую IDisposable: очистить неуправляемые ресурсы, отписаться о событиях, закрыть соединения

    Идиома, которую я использую для реализации IDisposable (, не threadsafe ):

    class MyClass : IDisposable {
        // ...
    
        #region IDisposable Members and Helpers
        private bool disposed = false;
    
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        private void Dispose(bool disposing) {
            if (!this.disposed) {
                if (disposing) {
                    // cleanup code goes here
                }
                disposed = true;
            }
        }
    
        ~MyClass() {
            Dispose(false);
        }
        #endregion
    }
    
    ответ дан olli-MSFT, с репутацией 2417, 11.02.2009
  • 11 рейтинг

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

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

    РЕДАКТИРОВАТЬ В ответ на комментарий Скотта:

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

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

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

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

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

    ответ дан Drew Noakes, с репутацией 178444, 11.02.2009
  • 10 рейтинг

    Да, этот код является полностью избыточным и ненужным, и он не заставляет сборщик мусора делать то, что он не сделал бы иначе (как только экземпляр MyCollection выходит из области видимости, то есть. ) Особенно звонки .Clear().

    Ответ на редактирование: Вроде. Если я сделаю это:

    public void WasteMemory()
    {
        var instance = new MyCollection(); // this one has no Dispose() method
        instance.FillItWithAMillionStrings();
    }
    
    // 1 million strings are in memory, but marked for reclamation by the GC
    

    Это функционально идентично этому для целей управления памятью:

    public void WasteMemory()
    {
        var instance = new MyCollection(); // this one has your Dispose()
        instance.FillItWithAMillionStrings();
        instance.Dispose();
    }
    
    // 1 million strings are in memory, but marked for reclamation by the GC
    

    Если вам действительно нужно освободить память в этот момент, позвоните по номеру GC.Collect(). Там нет причин делать это здесь, хотя. Память будет освобождена, когда это необходимо.

    ответ дан mquander, с репутацией 52478, 11.02.2009
  • 7 рейтинг

    Если вы хотите удалить прямо сейчас , используйте неуправляемую память .

    См .:

    ответ дан franckspike, с репутацией 1016, 3.06.2013
  • 6 рейтинг

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

    Public Class LargeStuff
      Implements IDisposable
      Private _Large as string()
    
      'Some strange code that means _Large now contains several million long strings.
    
      Public Sub Dispose() Implements IDisposable.Dispose
        _Large=Nothing
      End Sub
    

    Я понимаю, что реализация Disposable не соответствует текущим рекомендациям, но, надеюсь, вы все поняли идею.
    Теперь, когда вызывается Dispose, сколько памяти освобождается?

    Ответ: нет.
    Вызов Dispose может освободить неуправляемые ресурсы, он НЕ МОЖЕТ вернуть управляемую память, это может сделать только GC. Это не значит, что вышеизложенное не является хорошей идеей, следуя приведенному выше шаблону, на самом деле все еще хорошая идея. После запуска Dispose ничто не мешает GC повторно запросить память, которая использовалась _Large, даже если экземпляр LargeStuff все еще находится в области видимости. Строки в _Large также могут относиться к поколению 0, но экземпляр LargeStuff может относиться к поколению 2, поэтому снова память будет востребована раньше.
    Однако нет смысла добавлять финализатор для вызова метода Dispose, показанного выше. Это просто ЗАДЕРЖИТ повторное требование памяти, чтобы позволить финализатору работать.

    ответ дан pipTheGeek, с репутацией 2567, 11.02.2009
  • 5 рейтинг

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


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

    Однажды я прочитал код, который был простым 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 для создания более чистого кода.

    ответ дан Robert Paulson, с репутацией 15015, 11.02.2009
  • 4 рейтинг

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

    Вызов методов Clear () не является необходимым, и GC, вероятно, не сделал бы этого, если бы Dispose не сделал этого. , ,

    ответ дан Arjan Einbu, с репутацией 11500, 11.02.2009
  • 4 рейтинг

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

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

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

    ответ дан Pragmateek, с репутацией 8767, 15.06.2013
  • 3 рейтинг

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

    Если объекты, на которые ссылаются _theList или _theDict, ссылаются на другие объекты, то этот объект List<> или Dictionary<> не будет подлежать сбору, но вдруг не будет иметь содержимого. Если бы не было операции Dispose (), как в примере, эти коллекции все равно содержали бы свое содержимое.

    Конечно, если бы это была ситуация, я бы назвал это ломанным дизайном - я просто указываю (педантично, я полагаю), что операция Dispose() не может быть полностью избыточной, в зависимости от того, есть ли другие варианты использования List<> или Dictionary<>, которые не показаны во фрагменте.

    ответ дан Michael Burr, с репутацией 277528, 11.02.2009
  • 2 рейтинг

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

    Ярким примером являются циклические ссылки.

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

    Единственный способ обойти это - вручную разорвать циклические ссылки, установив родительские ссылки в null для дочерних элементов.

    Реализация IDisposable на родителя и детей является лучшим способом сделать это. Когда Dispose вызывается для Parent, вызывается Dispose для всех дочерних элементов, а в дочернем методе Dispose установите для родительских ссылок значение null.

    ответ дан controlbox, с репутацией 193, 14.09.2016
  • 2 рейтинг

    Первое определение. Для меня неуправляемый ресурс означает некоторый класс, который реализует интерфейс 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
        {
            /// 
    /// As usually I don't care was object disposed or not ///
     
            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;
            }
        }
    
    ответ дан Yura Zaletskyy, с репутацией 3305, 31.08.2015
  • 2 рейтинг

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

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

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

    ответ дан supercat, с репутацией 54774, 22.02.2012
  • 2 рейтинг

    IDisposable хорошо подходит для отписки от событий.

    ответ дан Adam Speight, с репутацией 363, 20.02.2012
  • 1 рейтинг

    Ваш пример кода не является хорошим примером для использования 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.
        }
    }
    
    ответ дан CharithJ, с репутацией 34893, 26.05.2017