Testing Finalizers and IDisposable

2019-06-20 18:02发布

问题:

The question is how can I test the fact that object disposes resources when finalise is called. The code for the class:

public class TestClass : IDisposable {

    public bool HasBeenDisposed {get; private set; }

    public void Dispose() {
        HasBeenDisposed = true;
    }

    ~TestClass() {
        Dispose();
    }
}

Please note that I don't care about the correct implementation of Dispose/Finalize just now as I want to find the way to test it first. At this stage it is enough to assume the HasBeenDisposed will be set to true if Dispose/Finalize ware called.

The actual test I wrote looks like:
UPDATED WITH WEAKREFERENCE:

[Test]
public void IsCleanedUpOnGarbadgeCollection() {
    var o = new TestClass();
    o.HasBeenDisposed.Should().Be.False();

    **var weak = new WeakReference(o, true); // true =Track after finalisation
    o = null; // Make eligible for GC**

    GC.Collect(0, GCCollectionMode.Forced);
    GC.WaitForPendingFinalizers();


    **((TestClass)weak.Target)**.HasBeenDisposed.Should().Be.True();
}

or the code I like better (ADDED AFTER UPDATE):

[Test]
public void IsCleanedUpOnGarbadgeCollection() {
    WeakReference weak = null;

    // Use action to isolate instance and make them eligible for GC
    // Use WeakReference to track the object after finalisaiton
    Action act = () = {
        var o = new TestClass();
        o.HasBeenDisposed.Should().Be.False();
        weak = new WeakReference(o, true); // True=Track reference AFTER Finalize
    };

    act();

    GC.Collect(0, GCCollectionMode.Forced);
    GC.WaitForPendingFinalizers();

    // No access to o variable here which forces us to use WeakReference only to avoid error
    ((TestClass)weak.Target).HasBeenDisposed.Should().Be.True();
}

This test fails (PASSES AFTER UPDATE) but I observe following (UPDATED):

  1. GC.WaitForPendingFinalizers() does suspend the thread and finalises instance in o, but only if is not rooted. Assigned NULL to it and used WeakReference to get it AFTER finalisation.
  2. Finilize (destructor) code is executed at correct point when the o does not hold the instance.

So what is the correct way of testing this. What do I miss?

I suppose it is the variable o that prevents GC from collection it.
UPDATE: Yes it is the issue. Had to use WeakReference instead.

回答1:

"I suppose it is the variable o that prevents GC from collection it." Correct. The existence of a reference on the stack means the object is reachable, and therefore not eligible for collection (and finalisation).

Because an object will not be finalised until there are no references to it, testing finalisation behaviour is likely to be tricky. (You need a reference to the object in order to make assertions about it!) One way is to do it indirectly: have the object send some sort of message during finalisation. But this distorts the finalisation code purely for test purposes. You could also hold a weak reference to the object, which would make it eligible for finalisation, and have it resurrect itself in the finaliser -- but again you don't want to have it resurrect itself in production code.



回答2:

Why would the object be collected if there is a local reference to it?



回答3:

A memory profiler is the most appropriate way to test for leaks.

I could recommend the .Net Memory Profiler.