简体   繁体   English

.NET对象事件和dispose / GC

[英].NET object events and dispose / GC

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... 编辑:在Joel Coehoorns的优秀答案之后,我明白我需要更加具体,所以我修改了我的代码,使其更贴近我正在努力理解的事情......

Events: As I understand, in the background the events are "collection" of EventHandlers aka Delegates which will be executed when event raised. 事件:据我所知,在后台,事件是EventHandlers又名代表的“集合”,将在事件引发时执行。 So for me it means that if object Y has event E and object X subscribes to event YE , 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. 所以对我来说,这意味着如果对象Y有事件E而对象X订阅了事件YE ,那么Y将引用X,因为Y必须执行位于X的方法,这样就不能收集X了 ,那个我理解的事情。

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

But it is not what Joel Coehoorn tells... 但这不是Joel Coehoorn所说的......

However, there is an issue with events such that sometimes people like to use IDisposable with types that have events. 但是,事件存在问题,有时人们喜欢将IDisposable与具有事件的类型一起使用。 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. 问题是当类型X订阅另一个类型Y中的事件时,X现在具有对Y的引用。该引用将阻止Y被收集。

I not understand how X will reference the Y ??? 我不明白X将如何引用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. 编辑2:好的,现在很明显,当订阅者订阅发布者事件时,它不会在订阅者中创建对发布者的引用。 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. 只有从发布者到订阅者的引用创建(通过EventHandler)...在这种情况下,当发布者在订阅者之前收集发布者(订阅者的生命周期大于发布者)时,没有问题。

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. 但是 ......据我所知,当GC收集出版商时,不能保证理论上,即使订阅者的生命周期比出版商大,也可能发生订阅者合法收集,但发行人仍未收集(我不知道)要知道,如果在最接近的GC周期内,GC将足够聪明,首先收集发布者,然后收集订阅者。

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). 无论如何,在这种情况下,由于我的订阅者没有直接引用发布者并且无法取消订阅该事件,我想让发布者实现IDisposable,以便在删除所有对他的引用之前将其处理掉(参见CustomNotificationSystemHandler in我的例子)。

AND AGAIN What I should write in publishers dispose method in order to clear all references to subscribers? 再次 ,我应该在发布商处理方法中写出什么来清除所有对订阅者的引用? should it be EventHappened -= null; 应该是EventHappened - = null; or EventHappened = null; 或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; 
      }
   }

The life time of object B is longer that A, so A may be disposed earlier 物体B的寿命比A长,因此A可以更早地处理

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. 不管你做什么都不会允许一个要收集它的父B.只要B是到达之前,所以是A.即使是私有的,它仍然是可到达的任何代码B内部,所以只要B是可达的, A被认为是可达的。 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. 这意味着垃圾收集器不确定您是否完成了它,并且在收集B之前永远不会收集A.即使您明确调用GC.Collect()或类似的东西也是如此。 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. 我甚至不确定你为什么要在这里实现IDisposable(它与内存或垃圾收集无关 ),但是我会给你带来怀疑的好处,因为我们只是看不到非托管资源。

Nothing prevents you from Disposing A whenever you want. 没有什么能阻止您随时处置A。 Just call a.Dispose(), and it's done. 只需调用a.Dispose(),就完成了。 The only way the .Net framework will ever call Dispose() for you automatically is at the end of using block. .Net框架将自动为您调用Dispose()的唯一方法是using块结束。 Dispose() is not called during garbage collection, unless you do it as part of the object's finalizer (more on finalizers in a moment). 在垃圾收集期间不会调用Dispose(),除非您将其作为对象的终结器的一部分(稍后更多关于终结器)。

When implementing IDisposable, you are sending a message to programmers that this type should (maybe even "must") be disposed promptly. 在实现IDisposable时,您正在向程序员发送一条消息,即此类型应该(甚至可能“必须”)立即处理。 There are two correct patterns for any IDisposable object (with two variations on the pattern). 任何IDisposable对象都有两种正确的模式(模式有两种变体)。 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(). 当这是不可能的时(例如:类型是其他类型的成员的代码),第二种模式是父类型也应该实现IDisposable,因此它本身可以包含在一个使用块中,它是Dispose()可以调用你的类型的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. 这些模式的变化是使用try / finally块而不是using块,在finally块中调用Dispose()。

Now on to finalizers. 现在到终结者。 The only time you need to implement a finalizer is for an IDisposable type that originates an unmanaged resource. 您需要实现终结器的唯一时间是用于发起非托管资源的IDisposable类型。 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. 因此,例如,如果上面的类型A只是包装类似SqlConnection的类,则它不需要终结器,因为SqlConnection本身的终结器将负责任何所需的清理。 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. 但是,如果您的类型A实现了与全新数据库引擎的连接,那么您需要一个终结器来确保在收集对象时关闭连接。 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. 但是,类型B不需要终结器,即使它管理/包装您的类型A,因为类型A将负责完成连接。

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. 但是,事件存在问题,有时人们喜欢将IDisposable与具有事件的类型一起使用。 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. 问题是,当类型X订阅另一个类型Y的事件时,Y现在具有对X的引用。该引用可以防止收集X. 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. 如果你期望Y的寿命比X长,那么你可能会遇到问题,特别是如果Y相对于随时间变化的许多X来说是非常长寿的。

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. 为了解决这个问题,有时程序员会使用Y类实现IDisposable,而Dispose()方法的目的是取消订阅任何事件,以便也可以收集订阅对象。 Technically, this is not the purpose of the Dispose() pattern, but it works well enough that I'm not going to argue about it. 从技术上讲,这不是Dispose()模式的目的,但它运作良好,我不会争论它。 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 如果这是实现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. 否则,将不会调用Dispose(),仍然无法收集您的对象。

In this case, your type A is private to type B, and so only type B can subscribe to A's events. 在这种情况下,您的类型A对于类型B是私有的,因此只有类型B可以订阅A的事件。 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. 由于'a'是B类的成员,因此在B不再可达之前,它们都不符合垃圾收集条件,此时两者将不再可访问,并且事件订阅引用将不计算。 That means a reference held on B by A's event would not prevent B from being collected. 这意味着A事件在B上持有的引用不会阻止B被收集。 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. 但是,如果您在其他地方使用A类型,您可能仍希望具有A实现IDisposable以确保您的事件已取消订阅。 If you do that, make sure to follow the whole pattern, such that instances of A are enclosed in using or try/finally blocks. 如果这样做,请确保遵循整个模式,以便A的实例包含在using或try / finally块中。

I have added My comments in your sample code. 我在您的示例代码中添加了我的评论。

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

   public void Dispose()
   {
      //Amit: If you have only one event 'EventHappened', 
      //you can clear up the subscribers as follows

      eventTable["EventHappened"] = null;

      //Amit: EventHappened = null will not work here as it is 
      //just a syntactical sugar to clear the compiler generated backing delegate.
      //Since you have added 'add' and 'remove' there is no compiler generated 
      //delegate to clear
      //
      //Above was just to explain the concept.
      //If eventTable is a dictionary of EventHandlers
      //You can simply call 'clear' on it.
      //This will work even if there are more events like EventHappened          
   }
}

class B
{          
   public B(A a)
   {
      a.EventHappened += new EventHandler(this.HandleEventB);

      //You are absolutely right here.
      //class B does not store any reference to A
      //Subscribing an event does not add any reference to publisher
      //Here all you are doing is calling 'Add' method of 'EventHappened'
      //passing it a delegate which holds a reference to B.
      //Hence there is a path from A to B but not reverse.
   }

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

class C
{          
   public C(A a)
   {
      a.EventHappened += new EventHandler(this.HandleEventC);
   }

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

class Service
{       
    A _a;

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

       _a = new A();

       //Amit:You are right all these do not store any reference to _a
       B b1 = new B(_a);
       B b2 = new B(_a);
       C c1 = new C(_a);
       C c2 = new C(_a);
    }

    void CustomNotificationSystemHandler(args)
    {

        //Amit: You decide that _a has lived its life and must be disposed.
        //Here I assume you want to dispose so that it stops firing its events
        //More on this later
        _a.Dispose();

        //Amit: Now _a points to a brand new A and hence previous instance 
        //is eligible for collection since there are no active references to 
        //previous _a now
        _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??? b1,b2,c1,c2将继续按原样存在,我知道他们现在将订阅以前的_a实例,并且我没关系,但是在那个例子中,现在,没有人引用前一个_a实例(b不是保留对_a)的引用,根据我的理论,以前的_a实例,现在可能会被收集......或者我错过了什么?

As explained through my comments in the above code, you are not missing anything here :) 正如我在上面的代码中的评论所解释的那样,你在这里没有遗漏任何东西:)

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. 但是......据我所知,当GC收集出版商时,不能保证理论上,即使订阅者的生命周期比出版商大,也可能发生订阅者合法收集,但发行人仍未收集(我不知道)要知道,如果在最接近的GC周期内,GC将足够聪明,首先收集发布者,然后收集订阅者。

Since publisher references subscriber, it can never happen that the subscriber becomes eligible for collection before the publisher but reverse can be true. 由于发布者引用订阅者,因此订阅者在发布者之前就没有资格收集,但反向可能是真的。 If publisher gets collected before subscriber then, as you said, there is no problem. 如果发布者在订阅者之前收集,那么正如您所说,没有问题。 If the subscriber belongs to a lower GC generation than publisher then since publisher holds a reference to subscriber, GC will treat the subscriber as reachable and will not collect it. 如果订阅者属于比发布者更低的GC代,那么由于发布者持有对订阅者的引用,因此GC会将订阅者视为可达,并且不会收集订阅者。 If both belong to same generation, they will be collected together. 如果两者都属于同一代,它们将被收集在一起。

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 由于我的订阅者没有直接引用发布者而且无法取消订阅该活动,我想让发布商实施IDisposable

Contrary to what some have suggested, I would recommend implementing dispose if at any point you are deterministically sure that the object is no longer required. 与某些人的建议相反,如果在任何时候你确定不再需要该对象,我建议实施dispose。 Simply updating an object reference may not always lead to an object stop publishing events. 简单地更新对象引用可能并不总是导致对象停止发布事件。

Consider the following code: 请考虑以下代码:

class MainClass
{
    public static Publisher Publisher;

    static void Main()
    {
        Publisher = new Publisher();

        Thread eventThread = new Thread(DoWork);
        eventThread.Start();

        Publisher.StartPublishing(); //Keep on firing events
    }

    static void DoWork()
    {
        var subscriber = new Subscriber();
        subscriber = null; 
        //Subscriber is referenced by publisher's SomeEvent only
        Thread.Sleep(200);
        //We have waited enough, we don't require the Publisher now
        Publisher = null;
        GC.Collect();
        //Even after GC.Collect, publisher is not collected even when we have set Publisher to null
        //This is because 'StartPublishing' method is under execution at this point of time
        //which means it is implicitly reachable from Main Thread's stack (through 'this' pointer)
        //This also means that subscriber remain alive
        //Even when we intended the Publisher to stop publishing, it will keep firing events due to somewhat 'hidden' reference to it from Main Thread!!!!
    }
}

internal class Publisher
{
    public void StartPublishing()
    {
        Thread.Sleep(100);
        InvokeSomeEvent(null);
        Thread.Sleep(100);
        InvokeSomeEvent(null);
        Thread.Sleep(100);
        InvokeSomeEvent(null);
        Thread.Sleep(100);
        InvokeSomeEvent(null);
    }

    public event EventHandler SomeEvent;

    public void InvokeSomeEvent(object e)
    {
        EventHandler handler = SomeEvent;
        if (handler != null)
        {
            handler(this, null);
        }
    }

    ~Publisher()
    {
        Console.WriteLine("I am never Printed");
    }
}

internal class Subscriber
{
    public Subscriber()
    {
        if(MainClass.Publisher != null)
        {
            MainClass.Publisher.SomeEvent += PublisherSomeEvent;
        }
    }

    void PublisherSomeEvent(object sender, EventArgs e)
    {
        if (MainClass.Publisher == null)
        {
            //How can null fire an event!!! Raise Exception
            throw new Exception("Booooooooommmm");
            //But notice 'sender' is not null
        }
    }
}

If you run the above code, more often than not you will receive the 'Booooooooommmm'. 如果您运行上述代码,通常会收到“Booooooooommmm”。 Hence idea is that event publisher must stop firing events when we are sure that its life is up. 因此,当我们确定它的生命已经结束时,事件发布者必须停止发射事件。

This can be done through Dispose method. 这可以通过Dispose方法完成。

There are two ways to achieve this: 有两种方法可以实现这一目标:

  1. Set a flag 'IsDisposed' and check it before firing any event. 设置一个标志'IsDisposed'并在触发任何事件之前检查它。
  2. Clear up the event subscribers list (as suggested in my comments in your code). 清除事件订阅者列表(如我的代码中的注释中所示)。

Benefit of 2 is that you release any reference to the subscribers, thereby enabling there collection (as I explained earlier even if the publisher is garbage but belongs to higher generation then it may still prolong collection of lower generation subscribers). 2的好处是你发布了对订阅者的任何引用,从而实现了收集(正如我之前解释的那样,即使发布者是垃圾但属于更高代,那么它仍然可以延长较低代订户的收集)。

Though, admittedly, it will be quite rare that you experience the demonstrated behavior due to 'hidden' reachability of the publisher but as you can see benefits of 2 are clear and are valid for all event publishers especially long living ones (Singletons anybody!!!). 虽然,诚然,由于出版商的“隐藏”可达性,您很少会体验到所表现出来的行为,但是您可以看到2的好处是明确的,并且对所有活动出版商尤其是长寿的出版商都有效(Singletons任何人! !)。 This itself makes it worth to implement Dispose and go with 2. 这本身就值得实现Dispose并使用2。

Contrary to what some other answers claim, events whose publisher's GC lifetime may exceed a subscriber's useful lifetime should be regarded as unmanaged resources . 与其他一些答案所声称的相反,发布者的GC生命周期可能超过订阅者的使用寿命的事件应被视为非托管资源 The term "unmanaged" in the phrase "unmanaged resource" doesn't mean "completely outside the world of managed code", but rather relates to whether objects require cleanup beyond that provided by the managed garbage collector. 短语“非托管资源”中的术语“非托管”并不意味着“完全在托管代码世界之外”,而是涉及对象是否需要超出托管垃圾收集器提供的清理。

For example, a collection may expose a CollectionChanged event. 例如,集合可能会公开CollectionChanged事件。 If objects of some other type which subscribes to such an event are repeatedly created and abandoned, the collection may end up holding a delegate reference to each and every such object. 如果重复创建和放弃订阅这样的事件的某些其他类型的对象,则该集合可能最终持有对每个这样的对象的委托引用。 If such creation and abandonment happens eg once per second (as might happen if the object in question were created in a routine that updates a UI window) the number of such references could grow by more than 86,000 for each day that the program was running. 如果这样的创建和放弃发生例如每秒一次(如果所讨论的对象是在更新UI窗口的例程中创建的话可能发生的话),那么对于程序运行的每一天,这样的引用的数量可能增加超过86,000。 Not a big problem for a program which is never run for more than a few minutes, but an absolute killer for a program which could be run for weeks at a time. 对于一个从未运行超过几分钟的程序来说,这不是一个大问题,但对于一个可以一次运行数周的程序来说,这绝对是一个杀手锏。

It's really unfortunate that Microsoft didn't come up with a better event-cleanup pattern in vb.net or C#. 非常不幸的是,微软没有在vb.net或C#中提出更好的事件清理模式。 There's seldom any reason why a class instance which subscribes to events shouldn't clean them up before it's abandoned, but Microsoft did nothing to facilitate such cleanup. 订阅事件的类实例在放弃它之前不应该清理它们很少有任何理由,但微软没有采取任何措施来促进这种清理。 In practice, one can get away with abandoning objects that are subscribed to events sufficiently often (because the event publisher will go out of scope around the same time as the subscriber) that the annoying level of effort necessary to ensure events get properly cleaned up doesn't seem worthwhile. 在实践中,人们可以放弃放弃经常订阅事件的对象(因为事件发布者将在与订阅者大约同一时间内超出范围),确保事件得到适当清理所需的烦人程度的努力不会看起来不值得。 Unfortunately, it's not always easy to predict all the cases where an event publisher might live longer than expected; 不幸的是,预测事件发布者可能比预期寿命更长的所有情况并不总是那么容易; if many classes leave events dangling, it's possible for huge amounts of memory to be uncollectable because one of the event subscriptions happens to belong to a long-lived object. 如果许多类使事件悬空,则可能无法收集大量内存,因为其中一个事件订阅恰好属于一个长期存在的对象。

Addendum in response to edit 回复编辑的补遗

If X were to subscribe to an event from Y and then abandon all references to Y , and if Y became eligible for collection, X would not prevent Y from being collected. 如果X要从Y订阅一个事件然后放弃对Y所有引用,并且如果Y资格收集,则X不会阻止Y被收集。 That would be a good thing. 那将是一件好事。 If X were to keep a strong reference to Y for the purpose of being able to dispose of it, such reference would prevent Y from being collected. 如果X为了能够处理它而对Y保持强烈的引用,这样的引用将阻止Y被收集。 That might arguably not be such a good thing. 这可能说不是一件好事。 In some situations it would be better for X to keep a long WeakReference (one constructed with the second parameter set to true ) to Y rather than a direct reference; 在某些情况下, X最好将长WeakReference (一个用第二个参数设置为true构造)保持为Y而不是直接引用; if the target of the WeakReference is non-null when X is Dispose d, it will have to unsubscribe from Y 's event. 如果当XDispose d时WeakReference的目标为非null,则必须取消订阅Y的事件。 If the target is null, it can't unsubscribe but it won't matter, because by then Y (and its reference to X ) will have completely ceased to exist. 如果目标为空,则无法取消订阅,但无关紧要,因为那时Y (及其对X引用)将完全不存在。 Note that in the unlikely event that Y dies and is resurrected, X will still want to unsubscribe its event; 请注意,在Y死亡并复活的不太可能的情况下, X仍然希望取消订阅其事件; using a long WeakReference will ensure that can still happen. 使用长WeakReference将确保仍然可以发生。

While some would argue that X shouldn't bother keeping a reference to Y , and Y should simply be written to use some sort of weak event dispatching, such behavior is not correct in the general case because there's no way for Y to tell whether X would do anything that other code might care about even if Y holds the only reference to X . 虽然有些人认为X不应该费心去保持对Y的引用,而Y应该简单地写成使用某种弱事件调度,这种行为在一般情况下是不正确的,因为Y无法判断是否X 即使Y持有对X的唯一引用,也会做其他代码可能关心的任何事情。 It is entirely possible that X might hold a reference to some strongly-rooted object, and might do something to that other object within its event handler. X可能完全可能包含对某些强根对象的引用,并且可能对其事件处理程序中的其他对象执行某些操作。 The fact that Y holds the only reference to X should not imply that no other objects are "interested" in X . Y持有对X的唯一引用的事实不应该暗示没有其他对象在X中“感兴趣”。 The only generally-correct solution is to have objects which are no longer interested in other objects' events notify the latter objects of that fact. 唯一通常正确的解决方案是让对其他对象事件不再感兴趣的对象通知后一个对象该事实。

我会让我的B类实现IDisposable,并且在它的dispose例程中,我首先检查A是否为null然后处理A.通过使用这种方法,你必须确保处理你的最后一个类和内部将处理所有其他处置。

You don't need to unhook event handlers when disposing of an object, although you may want to. 处理对象时,您不需要取消挂钩事件处理程序,尽管您可能需要 By that I mean that the GC will clean up event handlers just fine without any intervention on your part, however depending on the scenario you may want to remove those event handlers before the GC does in order to prevent the handler being called when you weren't expecting it. 我的意思是,GC会清理事件处理程序,而不需要您进行任何干预,但是根据情况,您可能希望在GC之前删除这些事件处理程序,以防止在您执行时调用处理程序。期待它。

In your example I think you have your roles reversed - class A shouldn't really be unsubscribing event handlers added by others and has no real need to remove event handlers eiether, as it can instead just stop raising those events! 在您的示例中,我认为您的角色已经颠倒了 - A类不应该取消订阅其他人添加的事件处理程序,并且没有真正需要删除事件处理程序,因为它可以反而只是停止提升这些事件!

Suppose however that the situation is reversed 然而,假设情况正好相反

class A
{
   public EventHandler EventHappened;
}

class B : IDisposable
{
    A _a;
    private bool disposed;

    public B(A a)
    {
        _a = a;
        a.EventHappened += this.HandleEvent;
    }

    public void Dispose(bool disposing)
    {
        // As an aside - if disposing is false then we are being called during 
        // finalization and so cannot safely reference _a as it may have already 
        // been GCd
        // In this situation we dont to remove the handler anyway as its about
        // to be cleaned up by the GC anyway
        if (disposing)
        {
            // You may wish to unsubscribe from events here
            _a.EventHappened -= this.HandleEvent;
            disposed = true;
        }
    }

    public void HandleEvent(object sender, EventArgs args)
    {
        if (disposed)
        {
            throw new ObjectDisposedException();
        }
    }
 }

If its possible for A to continue raising events even after B has been disposed, and the event handler for B could do something that may cause either an exception or some other unexpected behaviour if B is disposed then its probably a good idea to unsubscribe from this event first. 如果可能的A继续提高事件即使B已被释放,并为事件处理程序B是否可以做一些事情,可能会引起其他异常或其他一些意外行为B布置那么它可能是一个好主意,从这个退订事件第一。

MSDN Reference MSDN参考

"To prevent your event handler from being invoked when the event is raised, unsubscribe from the event. In order to prevent resource leaks, you should unsubscribe from events before you dispose of a subscriber object. Until you unsubscribe from an event, the multicast delegate that underlies the event in the publishing object has a reference to the delegate that encapsulates the subscriber's event handler. As long as the publishing object holds that reference, garbage collection will not delete your subscriber object." “为了防止在引发事件时调用事件处理程序,请取消订阅事件。为了防止资源泄漏,您应该在处置订阅者对象之前取消订阅事件。在您取消订阅事件之前,多播委托发布对象中事件的基础是对封装订阅者事件处理程序的委托的引用。只要发布对象持有该引用,垃圾收集就不会删除您的订阅者对象。“

"When all subscribers have unsubscribed from an event, the event instance in the publisher class is set to null." “当所有订阅者都取消订阅某个事件时,发布者类中的事件实例将设置为null。”

The object A references B through EventHandler delegate(A has an instance of EventHandler wich references B). 对象A通过EventHandler委托引用B(A具有引用B的EventHandler的实例)。 B don't have any reference to A. When A is set to null it will be collected and the memory will be freed. B没有任何对A的引用。当A被设置为null时,它将被收集并释放内存。 So you don't need to clear anything in this case. 所以在这种情况下你不需要清除任何东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM