我阅读了解释如何使用 Dispose 模式的优秀答案,以及它为什么会这样工作。
该帖子明确指出您希望在两种不同的情况下使用 Dispose 模式:
- 摆脱非托管资源(因为我们必须这样做)
- 摆脱托管资源(因为我们想提供帮助)
我的问题是:
- 当对象在其整个生命周期内订阅外部事件时,在 Dispose 方法中从该事件中注销也是常见/良好的做法吗?你会为此目的实现 IDisposable 接口吗?
我阅读了解释如何使用 Dispose 模式的优秀答案,以及它为什么会这样工作。
该帖子明确指出您希望在两种不同的情况下使用 Dispose 模式:
我的问题是:
是的你应该。
这是向您的类的消费者表明它具有必须释放的“资源”的最佳方式。(即使事件订阅在技术上不是资源)
Dispose
在许多(大多数?)情况下,一个对象在被调用后很快就会有资格进行垃圾收集。例如,对于IDisposable
使用 using 语句实例化的对象,这始终是正确的:
using(var myDisposableObject = ...)
{
...
} // myDisposableObject.Dispose() called here
// myDisposableObject is no longer reachable and hence eligible for garbage collection here
在这种情况下,在一般情况下,我个人不会因为删除事件订阅而使代码混乱。
例如,一个 ASP.NETPage
或UserControl
is IDisposable
,并且经常处理来自网页上其他控件的事件。Page
当or被释放时,没有必要删除这些事件订阅UserControl
,事实上,我从来没有见过一个 ASP.NET 应用程序可以这样做。
更新
其他回答者建议您始终取消订阅类Dispose
方法中的事件IDisposable
。
在一般情况下,我不同意这一点,尽管可能存在适当的应用程序特定情况。
合乎逻辑的结论是,任何订阅事件的类都应该是IDisposable
,以便它可以确定地取消订阅 - 我认为没有任何合乎逻辑的理由为什么该建议只适用于碰巧拥有非托管资源的类。我认为这不是一个好的一般建议,原因如下:
创建一个类IDisposable
以便它可以取消订阅事件会增加该类用户的复杂性。
取消订阅该Dispose
方法中的事件需要开发人员跟踪需要删除的事件订阅 - 有点脆弱,因为很容易错过一个(或维护开发人员添加一个)。
在类从长期发布者订阅事件的情况下,使用弱事件模式可能更合适,以确保订阅者的生命周期不受事件订阅的影响。
在许多情况下(例如,一个 ASP.NET Page 类从其子控件订阅事件),发布者和订阅者的生命周期密切相关,因此无需取消订阅。
我更喜欢双管齐下:
(1) 显式方法UnregisterFromExternalEvents();
(2)Dispose()
对该方法的调用。
这样,任何控制类实例的代码都可以显式取消注册,或者信任Dispose
以正确处理和处理此类事务。
是的,取消注册所有外部事件是一种很好的做法,但由于事件的松散耦合性质,尽管不是非常必要。它从事件生成器中删除订阅者对象的事件入口点引用,yes 会很有帮助。
对于 dispose 方法中取消订阅的部分也可以。Dispose 方法的经验法则是 - “Dispose 方法应该以一种方式卸载资源,如果多次调用 dispose 它仍然有效,即您应该在 dispose 中释放资源,并且只有一次。(这需要在 dispose 之前进行检查)资源)”
是的,这是一个非常好的主意。事件发布者持有对事件订阅者的引用,这将防止订阅者被垃圾收集。(请参阅事件处理程序是否会阻止垃圾收集发生?)
此外,如果您的事件处理程序使用您正在释放的资源,则事件处理程序(将继续由事件发布者调用)可能会在资源释放后产生异常。
因此,在释放资源之前从任何事件中取消注册很重要,尤其是在您使用异步或多线程时,因为在某些情况下,可能会在释放资源和从该事件中取消注册之间引发事件.
下面的代码演示了这一点:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ReleaseEvents
{
class Program
{
public static event EventHandler SomethingHappened;
static void Main( string[] args )
{
using ( var l_dependent = new Dependent() )
{
SomethingHappened( null, EventArgs.Empty );
}
// Just to prove the point, garbage collection
// will not clean up the dependent object, even
// though it has been disposed.
GC.Collect();
try
{
// This call will cause the disposed object
// (which is still registered to the event)
// to throw an exception.
SomethingHappened( null, EventArgs.Empty );
}
catch ( InvalidOperationException e )
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine( e.ToString() );
}
Console.ReadKey( true );
}
}
class Dependent : IDisposable
{
private object _resource;
public Dependent()
{
Program.SomethingHappened += Program_SomethingHappened;
_resource = new object();
}
private void Program_SomethingHappened( object sender, EventArgs e )
{
if ( _resource == null )
throw new InvalidOperationException( "Resource cannot be null!" );
Console.WriteLine( "SomethingHappened processed successfully!" );
}
public void Dispose()
{
_resource = null;
}
}
}
第二次SomethingHappened
引发事件时,Dependent
该类将引发 InvalidOperationException。您必须取消注册该事件以防止这种情况发生:
class Dependent : IDisposable
{
// ...
public void Dispose()
{
Program.SomethingHappened -= Program_SomethingHappened;
_resource = null;
}
}
当我第一次尝试实现 MVVM 架构时,我实际上遇到了这个问题。当我在 ViewModel 之间切换时,我只是释放了我认为唯一的参考(一个 ActiveViewModel 属性)。我没有意识到我的 ViewModel 订阅的事件将它保存在内存中。随着应用程序运行时间的延长,它变得越来越慢。最终我意识到,我认为我已经发布的 ViewModels 实际上正在继续处理事件(昂贵的)。我必须明确释放事件处理程序来解决这个问题。
感谢这个问题,我将把我的课程写成:
class Foo : IDisposable
{
public event EventHandler MyEvent;
/// <summary>
/// When disposing unsubscibe from all events
/// </summary>
public void Dispose()
{
if (MyEvent != null)
{
foreach (Delegate del in MyEvent.GetInvocationList())
MyEvent -= (del as EventHandler);
}
// do the same for any other events in the class
// ....
}
}