C # одноразовые предметы - PullRequest
7 голосов
/ 26 апреля 2011

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

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

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

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


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

SomeObject* AllocateAndConstruct();

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

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

Ответы [ 7 ]

6 голосов
/ 26 апреля 2011

Если вы хотите определенным образом распорядиться объектами в коллекции, вы должны вызвать 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 #, если вы не утилизируете свой объект, сборщик мусора в конечном итоге запустится и очистит его.

6 голосов
/ 26 апреля 2011

(из вопроса)

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

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

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

* * Тысяча двадцать-одиной т.е.
using(var imageWrapper = GetImages()) {
    foreach(var image in imageWrapper) {
         ...
    }
    // etc
} // assume imageWrapper is something you write, which disposes the child items

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

5 голосов
/ 26 апреля 2011

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

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

1 голос
/ 26 апреля 2011

Вы можете использовать блок '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 ++.

0 голосов
/ 05 мая 2014

Отличный метод - создать собственный класс коллекции, который реализует 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. Кстати: если вы загрузили растровое изображение из файла и забыли удалить (),растровое изображение, вы не знаете, когда вы можете удалить этот файл.

0 голосов
/ 05 мая 2014

Отличный метод - создать собственный класс коллекции, который реализует 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. Кстати: если вы загрузили растровое изображение из файла и забыли удалить () растровое изображение, вы не знаете, когда сможете удалить этот файл.

0 голосов
/ 26 апреля 2011

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

...