다음을 통해 공유


Disposable Collection

Somebody asked me today, if we have a collection that is disposable and calls dispose on all its items when the collections.Dispose method is called. You can implement such collection by inheriting from List<T> and adding Dispose logic. If you add one additional requirement that Dispose is called on the items before they are removed from the collection when the collection is cleared, you need to use Collection<T> which can be customized more than List<T>. Here is a quick custom collection that does what I am talking about.

public class DisposableCollection<T> : Collection<T>, IDisposable where T: IDisposable {
protected virtual void Dispose(bool disposing){
if(disposing){
foreach(T item in this){
item.Dispose();
}
}
}
public void Dispose(){
Dispose(true);
}

   protected override void ClearItems(){
this.Dispose();
base.ClearItems();
}
}

There are two problems with this collection though: a) what happens when an item is removed from the collection by calling Remove. b) what should the user do if they hold a reference to an item residing in the collection and then call Clear, which causes the item pointed to by the reference to be disposed:

var col = DisposableCollection<Foo>();
col.Add(new Foo());
Foo f = col[0];
col.Clear();
// now f points to a disposed object

… maybe Clear should not be disposing the items. What do you think?

Comments

  • Anonymous
    February 10, 2006
    Additionally, what if one of the Items in Dispose throws?  When you use nested "using" statements to manage disposal, your objects will still (usually) be disposed if something throws because the dispose is in a finally block.  In the case of a disposing collection, any objects after the exception will not be disposed.  This is a big problem if you are handling exceptions and attempting to continue.  Those items will have to wait for garbage collection to be disposed, which may be problematic.  That's something I've struggled with when creating a "disposing collection" like this.  A recursive "pop and dispose" algorithm would allow everything to at least get a chance to dispose.

  • Anonymous
    February 10, 2006
    I would say that a "Disposable" collection is more like a commodity, not a good programming practice. It's difficult to choose how to implement things like Clear because we're basically going back to problems that we had in C/C++, how's responsability is to delete an object ? The container ? The client of the container ? If you take a look at STL or MFC collection classes none of them has a collection that does delete when you "clear" the collection.

  • Anonymous
    February 10, 2006
    The comment has been removed

  • Anonymous
    February 10, 2006
    In the first case, the collection owns the disposable objects, and when you dispose the collection, other users of the collection items hold references to Disposed objects.  It's no diffferent than

    Font font1 = new Font("Tahoma", 8);

    Font font2 = font1;

    font1.Dispose();

    // Gee, font2 isn't of much use.

    As for remove, how about overriding the Remove method and Dispose the removed object?

  • Anonymous
    February 10, 2006
    Refcounting anyone ?

  • Anonymous
    February 13, 2006
    Clear should never dispose the objects in the collection. This is because the objects may be resused somewhere or transferred to another collection, even if they are "owned" by the cleared collection.

    If you make Clear dispose objects you will cause a lot of grief for yourself if you use these collections in a visual studio designer.

    Here is how we do it in VG.net.

    A DisposeAll method can be used to dispose all children. The Clear method detaches children from their parent. A Remove method also detaches a child from a parent. Detached objects can be reused in other collections.

    If you Add an object to a collection, already contained by another colleciton, and it is an "owned" object, we remove it from the original collection, as we allow only one owner of such objects. In our system we enforice the one owner per object constraint consistently this way. If you need to put an identical object into another collection, you can use Clone, or a CopyTo or CopyFrom method.

  • Anonymous
    February 13, 2006
    The comment has been removed

  • Anonymous
    February 13, 2006
    Also ref-counting is great, but in that case I don't think we should use the word "owned" or "contained" -- it becomes a collection of references, and generally those collections should be hidden from public.

  • Anonymous
    March 03, 2006
    The comment has been removed

  • Anonymous
    November 22, 2006
    The comment has been removed

  • Anonymous
    July 18, 2007
    You might want to check item vs. null before attempting to call item.Dispose().

  • Anonymous
    July 18, 2007
    P.S.  I would disagree with Mr. Levine - there is no need to call SuppresFinalize, as the class does not implement a finalizer.