C# одноразовые предметы

Есть ли какие-то советы о том, как я должен иметь дело с IDisposable последовательности объектов?

Например, у меня есть метод, который создает IEnumerable<System.Drawing.Image> Последовательность и в какой-то момент мне придется избавиться от этих объектов вручную, потому что в противном случае это может привести к некоторым утечкам.

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

**Или, может быть, вы могли бы посоветовать мне другой подход? **


Как правило, это та же проблема, что и, например, в неуправляемых C++ без общих указателей, где вы можете иметь метод:

SomeObject* AllocateAndConstruct();

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

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

7 ответов

Решение

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

Это "ранее" зависит от вас, чтобы определить, например, вы можете освободить их, когда окно, которое их использует, закрывается, или когда ваша единица работы завершает выполнение каких-либо операций с ними. Но в какой-то момент некоторый объект должен "владеть" этими ресурсами и, следовательно, должен знать, когда они больше не нужны.

(из вопроса)

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

GC не происходит сразу, когда ваш объект выходит за пределы области видимости; это недетерминировано. К тому времени, когда GC увидит это, нет смысла делать что-либо еще (что еще не обработано финализатором), так как это слишком поздно.

Хитрость заключается в том, чтобы узнать, когда вы закончили с этим, и позвонить Dispose() сам. Во многих случаях using добивается этого. Например, вы можете написать класс, который реализует IDisposable и инкапсулирует набор Images - и оберните ваше использование этого инкапсулирующего объекта с using, Dispose() на обертке Dispose() все изображения проведены.

т.е.

using(var imageWrapper = GetImages()) {
    foreach(var image in imageWrapper) {
         ...
    }
    // etc
} // assume imageWrapper is something you write, which disposes the child items

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

Если вы хотите определенным образом избавиться от объектов в коллекции, вы должны вызвать Dispose на каждой:

myImages.ToList().ForEach(image => image.Dispose());

Если вы этого не сделаете, и если ваши объекты станут недоступными, GC в конечном итоге запустится и выпустит их.

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

using (myImages.AsDisposable()) { 
  // ... process the images
}

Это необходимая "инфраструктура":

public class DisposableCollectionWrapper<D> : IDisposable
where D : IDisposable {

  private readonly IEnumerable<D> _disposables;

  public DisposableCollectionWrapper(IEnumerable<D> disposables) {
    _disposables = disposables;
  }

  public void Dispose() {
    if (_disposables == null) return;
    foreach (var disposable in _disposables) {
      disposable.Dispose();
    }
  }

}

public static class CollectionExtensions {

  public static IDisposable AsDisposable<D>(this IEnumerable<D> self)
  where D : IDisposable {
    return new DisposableCollectionWrapper<D>(self);
  }

}

Также обратите внимание, что это не то же самое, что вы описали в C++. В C++, если вы этого не сделаете delete ваш объект, у вас есть настоящая утечка памяти. В C#, если вы не утилизируете свой объект, сборщик мусора в конечном итоге запустится и очистит его.

Вы можете использовать блок "using", чтобы удостовериться, что IDisposable удаляется, как только блок остается. Компилятор инкапсулирует такие блоки в операторы try - finally, чтобы убедиться, что Dispose вызывается в любом случае при выходе из блока.

Используя финализатор, можно заставить GC вызывать метод Dispose для тех объектов, которые как-то "пропустили". Однако реализация финализатора обходится дороже и снижает эффективность сборки мусора и, возможно, общую производительность вашего приложения. Поэтому, если это возможно, вы должны попытаться утилизировать ваши IDisposables самостоятельно; детерминировано:

public class Context : IDisposable {

    List<IDisposable> m_disposable = new List<IDisposable>();
    public void AddDisposable(IDisposable disposable) {
        m_disposable.Add(disposable); 
    }

    public void Dispose() {
        foreach (IDisposable disp in m_disposable)
            disp.Dispose(); 
    }

    // the Context class is used that way: 
    static void Main(string[] args) {

        using (Context context = new Context()) {
            // create your images here, add each to the context
            context.AddDisposable(image); 
            // add more objects here 

        } // <- leaving the scope will dispose the context
    }
}

При использовании некоторого умного дизайна процесс добавления объектов в контекст может стать еще проще. Можно дать контекст методу создания или опубликовать его через статический синглтон. Таким образом, он будет доступен и для дочерних методов - без необходимости передавать ссылку на контекст. Используя эту схему, можно даже имитировать функциональность искусственного деструктора, например, известную из C++.

Удобный метод - создать собственный класс коллекции, который реализует IDisposable. Когда этот класс коллекции имеет значение Disposed(), запросите для каждого элемента, реализует ли он IDisposed, и если да, то утилизируйте его.

Пример (посмотрите в другом месте, если вы не знаете о шаблоне IDisposable)

public class MyDisposableList<T> : List<T> : IDisposable
{
    private bool disposed = false;

    ~MyDisposableList()
    {
        Dispose(false);
    }

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

    protected void Dispose(bool disposing)
    {
        if (!disposed)
        {
            foreach (T myT in this)
            {
                IDisposable myDisposableT = myT as IDisposable;
                if (myDisposableT != null)
                {
                    myDisposableT.Dispose();
                }
                myT = null;
            }
            this.Clear();
            this.TrimExcess();
            disposed = true;
        }
    }
    ...
}

использование:

using (MyDisposableList<System.Drawing.Bitmap> myList = new ...)
{
    // add bitmaps to the list (bitmaps are IDisposable)
    // use the elements in the list
}

В конце оператора using автоматически удаляется myList и, следовательно, все битовые карты в myList. Кстати: если вы загрузили растровое изображение из файла и забыли удалить () растровое изображение, вы не знаете, когда сможете удалить этот файл.

Удобный метод - создать собственный класс коллекции, который реализует IDisposable. Когда этот класс коллекции имеет значение Disposed(), запросите для каждого элемента, реализует ли он IDisposed, и если да, то утилизируйте его.

Пример (посмотрите в другом месте, если вы не знаете о шаблоне IDisposable)

public class MyDisposableList<T> : List<T> : IDisposable
{
    private bool disposed = false;

    ~MyDisposableList()
    {
        Dispose(false);
    }

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

    protected void Dispose(bool disposing)
    {
        if (!disposed)
        {
            foreach (T myT in this)
            {
                IDisposable myDisposableT = myT as IDisposable;
                if (myDisposableT != null)
                {
                    myDisposableT.Dispose();
                }
                myT = null;
            }
            disposed = true;
        }
    }
    ...
}

использование:

using (MyDisposableList<System.Drawing.Bitmap> myList = new ...)
{
    // add bitmaps to the list (bitmaps are IDisposable)
    // use the elements in the list
}

В конце оператора using автоматически удаляется myList и, следовательно, все битовые карты в myList. Кстати: если вы загрузили растровое изображение из файла и забыли удалить () растровое изображение, вы не знаете, когда сможете удалить этот файл.

Ты можешь позвонить GC.Collect() если вы действительно должны были избавиться от этих объектов сразу, но, насколько я понимаю, именно ГК решает, собирать ли память.
Это в свою очередь вызовет Finalize() метод для каждого объекта, который должен быть освобожден.
Обратите внимание, что если коллекция выходит из области видимости, GC в конечном итоге будет собирать память, используемую изображениями.
Вы также можете использовать конструкцию using, если используете коллекцию, которая реализует IDisposeable. Это будет гарантировать, что объекты будут удалены именно тогда, когда коллекция выйдет из области видимости (или почти после окончания области видимости).

Другие вопросы по тегам