7

I have some code in a WPF application that looks like this:

public class MyTextBox : System.Windows.Controls.TextBox, IDisposable
{
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        Dispatcher.BeginInvoke((Action) delegate
        {
            // do work on member variables on the UI thread.
        });
    }

    ~MyTextBox()
    {
        Dispose(false);
    }
}

The dispose method is never getting explicitly called so the destructor calls it. It seems like in this case the object would be destroyed before the delegate in the BeginInvoke fires on the UI thread. It appears to be working though. What is happening here? Is this safe?

JonDrnek
  • 1,414
  • 3
  • 19
  • 37
  • for more fun stuff about finalizers, check out [When everything you know is wrong, part two](http://ericlippert.com/2015/05/21/when-everything-you-know-is-wrong-part-two/) – default May 27 '15 at 14:11
  • This is object resurrection. I have never seen that in practice before. Likely, your TextBox should not *have* a finalizer (what are you trying to do?). – usr May 27 '15 at 14:44
  • besides being a bad idea, i don't see any good use for this... – AK_ May 27 '15 at 15:05
  • I've not heard back from the original developer why this was being done. My interest here was more what actually happens when you do this. I think my confusion was equating the destructor being called and being garbage collected. I need to read the linked resources better though – JonDrnek May 27 '15 at 15:20
  • Eric Lippert has just written about finalizers. [This is definitely worth a look](http://ericlippert.com/2015/05/18/when-everything-you-know-is-wrong-part-one/) – Frank J May 27 '15 at 15:31

2 Answers2

4

It seems like in this case the object would be destroyed before the delegate in the BeginInvoke fires on the UI thread

The finalizer queues work to the UI message loop. The object may finish running its finalizer method before the actual delegate gets invoked on the UI thread, but that doesn't matter, as the delegate gets queued regardless.

What is happening here?

You're queueing work to the UI from the finalizer.

Is this safe?

Safe is a broad term. Would I do this? Definitely not. It looks and feels weird that you're invoking manipulation of UI elements from a finalizer, especially given this is a TextBox control. I suggest you get the full grasp of what running a finalizer guarantees and doesn't guarantee. For one, running a finalizer doesn't mean the object gets cleaned up in memory right away.

I'd also suggest reading @EricLippert posts: Why everything you know is wrong, Part1 & Part2

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • It is *impossible* for the member of the class to have been collected already. We know this because it's being accessed form a valid managed reference, so the GC isn't *allowed* to collect it. If he's accessing any of the *unmanged* resources that he's already cleaned up as a part of his disposal, then that could cause problems. – Servy May 27 '15 at 15:12
  • @Servy For some reason I thought once in the f-reachable queue it is considered dead (not a root). I deleted the irrelevant part. – Yuval Itzchakov May 27 '15 at 15:19
  • Actually the opposite. The fact that it is in the freachable queue means that it is very much alive. It will have lost its automatic registration for finalization though so if the method sent to BeginInvoke ends up resurrecting the object, it may need to reregister for finalization. The main problem is that once in finalization you've lost all sense of *when* this happens. If the application is being torn down, *why* would you queue up more work for the dispatcher? **This is definitely the wrong way to do whatever it is being done!** – Lasse V. Karlsen May 27 '15 at 15:47
  • @Lasse Definitely. I refreshed my memory as for the f-reachable queue and it is definitely alive. – Yuval Itzchakov May 27 '15 at 16:02
3

When you call BeginInvoke you're adding a delegate to a queue in the dispatcher, and that delegate is going to be pointing to an object that has a reference to the object that Dispose was called on. Since there is a reference to the object accessible through a rooted variable, this object is not eligible for collection.

Now, this is going to be extremely confusing to others using this code, so you should try to avoid "reanimating" objects that have already been finalized if at all possible.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • this depends on what he does inside the function there... it might not reanimate the object... – AK_ May 27 '15 at 15:09
  • @AK_ It's reanimating the object at least until the action finishes executing, and since that method is trying to treat the object as a valid object, after it has been disposed, I would consider that a form of reanimation. Of course, it's inherently a phrase that's not being technical, and so isn't as precise. The precise statement, as was said earlier, is that the object cannot be collected as long as that delegate is holding an (indirect) reference to the object. – Servy May 27 '15 at 15:11
  • why would the delegate hold a reference to the object? – AK_ May 27 '15 at 15:17
  • @AK_ The delegate is going to hold a reference to a compiler generated class that is used to create a named method out of the anonymous method. That compiler generated class will have a field whose value will be set to the object currently being disposed, because that object instance is being closed over by the anonymous method (since it's accessing fields of `this` in the anonymous method). So the delegate has an instance of this anonymous method (and a pointer to the method representing the anonymous method) and that object instance has a reference to the object being disposed. – Servy May 27 '15 at 15:21
  • IIRC that will only happen if you reference the enclosing class or its fields. I mean, if for example he'll just print something in the anonymous method, I think it shouldn't capture the enclosing object. though i honestly don't remember precisely... – AK_ May 27 '15 at 15:27
  • @AK_ He specifically said in his question that the anonymous method will be accessing fields of the object. See the comment he has in the method body. – Servy May 27 '15 at 15:37