.NET object events and dispose / GC

2019-01-17 23:27发布

EDIT: After Joel Coehoorns excellent answer, I understand that I need to be more specific, so I modified my code to be closer to thing I'm trying to understand...

Events: As I understand, in the background the events are "collection" of EventHandlers aka Delegates which will be executed when event raised. So for me it means that if object Y has event E and object X subscribes to event Y.E, then Y will have reference to X, since Y must execute the method located in X, in that way, X can not be collected, and that thing i understand.

//Creates reference to this (b) in a.
a.EventHappened += new EventHandler(this.HandleEvent);

But it is not what Joel Coehoorn tells...

However, there is an issue with events such that sometimes people like to use IDisposable with types that have events. The problem is that when a type X subscribes to events in another type Y, X now has a reference to Y. This reference will prevent Y from being collected.

I not understand how X will reference the Y ???

I modified a bit my example to illustrate my case more closer:

class Service //Let's say it's windows service that must be 24/7 online
{       
    A _a;

    void Start()
    {
       CustomNotificationSystem.OnEventRaised += new EventHandler(CustomNotificationSystemHandler)
       _a = new A();

       B b1 = new B(_a);
       B b2 = new B(_a);
       C c1 = new C(_a);
       C c2 = new C(_a);
    }

    void CustomNotificationSystemHandler(args)
    {

        //_a.Dispose(); ADDED BY **EDIT 2***
        a.Dispose();

        _a = new A();
        /*
        b1,b2,c1,c2 will continue to exists as is, and I know they will now subscribed
        to previous instance of _a, and it's OK by me, BUT in that example, now, nobody
        references the previous instance of _a (b not holds reference to _a) and by my
        theory, previous instance of _a, now may be collected...or I'm missing
        something???
        */
    }

}  

class A : IDisposable
        {
           public event EventHandler EventHappened;
        }

        class B
        {          
           public B(A a) //Class B does not stores reference to a internally.
           {
              a.EventHappened += new EventHandler(this.HandleEventB);
           }

           public void HandleEventB(object sender, EventArgs args)
           {
           }
        }

        class C
        {          
           public C(A a) //Class B not stores reference to a internally.
           {
              a.EventHappened += new EventHandler(this.HandleEventC);
           }

           public void HandleEventC(object sender, EventArgs args)
           {
           }
        }

EDIT 2: OK, now it's clear, when subscriber subscribes to a publishers events, it's NOT creates a reference to the publisher in subscriber. Only the reference from publisher to subscriber created (through EventHandler)...in this case when publisher collected by GC before the subscriber (subscribers lifetime is greater then publishers), there's no problem.

BUT...as I know, it's not guaranteed when GC will collect the publisher so in theory, even if subscribers lifetime is greater then publishers, it can happen that subscriber is legal for collection, but publisher is still not collected (I don't know if within closest GC cycle, GC will be smart enough to collect publisher first and then subscriber.

Anyway, in such case, since my subscriber do not have direct reference to publisher and can't unsubscribe the event, I would like to make publisher to implement IDisposable, in order to dispose it before delete all references to him (see in CustomNotificationSystemHandler in my example).

AND AGAIN What I should write in publishers dispose method in order to clear all references to subscribers? should it be EventHappened -= null; or EventHappened = null; or there's no way to do it in such way, and I need to make something like below ???

public event EventHandler EventHappened
   {
      add 
      {
         eventTable["EventHappened"] = (EventHandler)eventTable["EventHappened"] + value;
      }
      remove
      {
         eventTable["EventHappened"] = (EventHandler)eventTable["EventHappened"] - value; 
      }
   }

7条回答
唯我独甜
2楼-- · 2019-01-18 00:23

The life time of object B is longer that A, so A may be disposed earlier

It sounds like you are confusing "Disposal" with "Collection"? Disposing an object has nothing to do with memory or garbage collection. To make sure everything is clear, let's break up the two scenarios, and then I'll move on to events at the end:

Collection:

Nothing you do will ever allow A to be collected before it's parent B. As long as B is reachable, so is A. Even though A is private, it's still reachable from any code within B, and so as long as B is reachable, A is considered reachable. This means the garbage collector doesn't know for sure that you're done with it, and will never collect A until it is also safe to collect B. This is true even if you explicitly call GC.Collect() or similar. As long an object is reachable, it will not be collected.

Disposal:

I'm not even sure why you are implement IDisposable here (it has nothing to do with memory or garbage collection), but I'll give you the benefit of the doubt for the moment that we just don't see the unmanaged resource.

Nothing prevents you from Disposing A whenever you want. Just call a.Dispose(), and it's done. The only way the .Net framework will ever call Dispose() for you automatically is at the end of using block. Dispose() is not called during garbage collection, unless you do it as part of the object's finalizer (more on finalizers in a moment).

When implementing IDisposable, you are sending a message to programmers that this type should (maybe even "must") be disposed promptly. There are two correct patterns for any IDisposable object (with two variations on the pattern). The first pattern is to enclose the type itself in a using block. When this is not possible (for example: code such as yours where the type is a member of another type), the second pattern is that the parent type should also implement IDisposable so it can itself then be included in a using block, and it's Dispose() can call your type's Dispose(). The variation on these patterns is to use try/finally blocks instead of a using block, where you call Dispose() in the finally block.

Now on to finalizers. The only time you need to implement a finalizer is for an IDisposable type that originates an unmanaged resource. So, for example, if your type A above is just wrapping a class like SqlConnection, it does not need a finalizer because the finalizer in SqlConnection itself will take care of any needed cleanup. But, if your type A were implementing a connection to a whole new kind of database engine, you would want a finalizer to make sure your connections are closed when the object is collected. Your type B, however, would not need a finalizer, even though it manages/wraps your type A, because type A will take care of finalizing the connections.

Events:

Technically, events are still managed code and shouldn't need to be disposed. However, there is an issue with events such that sometimes people like to use IDisposable with types that have events. The problem is that when a type X subscribes to events in another type Y, Y now has a reference to X. This reference can prevent X from being collected. If you expected Y to have a longer lifetime then X, you can run into problems here, particularly if Y is very long-lived relative to many X's that come and go over time.

To get around this, sometimes programmers will have type Y implement IDisposable, and the purpose of the Dispose() method is to unsubscribe any events so that subscribing objects can also be collected. Technically, this is not the purpose of the Dispose() pattern, but it works well enough that I'm not going to argue about it. There are two things you need to know when using this pattern with events:

  1. You do not need a finalizer if this is the only reason for implementing IDisposable
  2. Instances of your type still need a using or try/finally block, or you haven't gained anything. Otherwise Dispose() will not be called and your objects still cannot be collected.

In this case, your type A is private to type B, and so only type B can subscribe to A's events. Since 'a' is a member of type B, neither is eligible for garbage collection until B is no longer reachable, at which point both will no longer be reachable and the event subscription reference won't count. That means a reference held on B by A's event would not prevent B from being collected. However, if you use the A type in other places, you may still want to have A implement IDisposable to make sure your events are unsubscribed. If you do that, make sure to follow the whole pattern, such that instances of A are enclosed in using or try/finally blocks.

查看更多
登录 后发表回答