Pregunta

I've created a class BinaryTree<T>. I wrote the following to dispose it:

public override void Dispose()
{
    this.Key = null;
    //delete this.Value;
    if(this.LeftLeaf != null) this.LeftLeaf.Dispose();
    this.LeftLeaf = null;
    if(this.RightLeaf != null) this.RightLeaf.Dispose();
    this.RightLeaf = null;
    base.Dispose();
}

My question is how do I mark the "T Value" as ready for disposal? I can't set it to null because you can't set generics to null. T could be a giangantic class, but it may not be using IDisposable so you can't call Dispose on it. Since this is "safe" class written like an "unsafe" class I don't trust GC to figure out it's not being used anymore.

¿Fue útil?

Solución

If for some reason you're writing a generic collection in which you know that the elements of your collection will implement IDisposable and the collection will be responsible for disposing of them (which, generally is something you would not expect of the collection) then you should be adding the generic constraint where T : IDisposable so that you can dispose of all of the items.

When it comes to normal collection of managed types, as opposed to unmanaged resources, you don't need to do anything. There's no need to set the fields to null, or the value to null. If the entire collection isn't referenced anywhere then the GC will collect it, and any values not referenced anywhere, even without you nulling out all fields.

I don't trust GC to figure out it's not being used anymore

That's going to make C# pretty much unusable for you as a language. By design, not only do you not have to manually release managed resources, you can't even if you wanted to. There is no way for you to force a managed object to be cleaned up immediately. You can only manually clean up unmanaged resources. If, for some reason, this is unacceptable for your application and you must have complete control over the memory management of your program you'll want to use some other language with more explicit memory management (or perhaps write some particularly sensitive parts in another language, and interop with C# for the rest).

Otros consejos

This will work for all T's (classes or structs):

public void DisposeIfApplicable(ref T value)
{
    if(value is IDisposable)
    {
        ((IDisposable)value).Dispose();
    }
}

or just inline it as

if(this.LeftLeaf is IDisposable)
{
    ((IDisposable)this.LeftLeaf).Dispose();
}

if(this.RightLeaf is IDisposable)
{
    ((IDisposable)this.RightLeaf).Dispose();
}

Edit: I didn't have the ref for the DisposeIfApplicable function, thus any structs will stay un-disposed (the passed copy would get disposed, though).

Most generic abstract data type implementations (e.g. List, Dictionary) don't implement IDisposable nor do they dispose of the contained objects, since that's "outside of the scope" of what that type should be doing. As such, in your class I would simply do:

this.LeftLeaf = default(T);

To remove the references, or:

this.LeftLeaf = null;

If you specify in the generic constraints that where T : class

It really should be the higher level consumer of your BinaryTree class that would worry about disposing of anything contained in this tree.

If you want to be able to set a T value to null, you can specify a class constraint on your T generic. That will force your tree to only accept classes for your T generic parameter. You can also use as IDisposable with null checks to see if you can call .Dispose():

public class MyBinaryTree<T>
    where T : class
{
    /* ... class code here ... */

    public override void Dispose()
    {
        this.Key = null;

        //delete this.Value;
        var leftLeafDisposable = this.LeftLeaf as IDisposable;
        if(leftLeafDisposable != null) leftLeafDisposable.Dispose();            
        this.LeftLeaf = null;

        var rightLeafDisposable = this.LeftLeaf as IDisposable;
        if(rightLeafDisposable != null) rightLeafDisposable.Dispose();
        this.RightLeaf = null;

        base.Dispose();
    }
}    

There are very few times code should check if something implements IDisposable and dispose it if so; in nearly every case, the requirement stems from a factory method whose return type should have implemented IDisposable but didn't (e.g. IEnumerator.GetEnumerator). Unless one is calling such a factory, one should not call Dispose on objects one receives but is not particularly expecting to hold resources, even the actual object instances happen to implement IDisposable.

The compiler-generated types for C# iterators, for example, will implement IDisposable; calling Dispose on an iterator during the lifetime of the first enumerator it returns from a GetEnumerator call will invalidate that enumerator and force any finally blocks within it to run. The call won't invalidate the iterator, nor will it affect any enumerators other than the first. The fact that the type of iterator instances implements IDisposable is an implementation detail rather than part of their contract, and client code should ignore it.

Rereading your question, I think I may have misunderstood it previously. If something which holds a T (e.g. an array of type T[]) is needed, but a particular T within it is not, you should set that particular T to default(T).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top