假设我有一个C#类,如下所示:
public class MyClass {
public SomeObject TheObject { get; }
public MyClass() {
TheObject = new SomeObject();
TheObject.MyEvent += MyEventHandler;
}
private void MyEventHandler() {
// some code
}
}
该类创建一个名为TheObject的内部对象,类型为SomeObject,并为此对象上的事件添加事件处理程序。
由于TheObject是一个公共属性,这意味着任何其他代码都可以维护指向该对象的指针;反过来,这将使MyClass类型的对象保持活动状态,因为TheObject有一个以事件处理程序的形式指向MyClass的指针。
因此,我认为保持此代码安全的唯一方法是向MyClass添加终结器:
public ~MyClass() {
TheObject?.MyEvent -= MyEventHandler;
}
这太糟糕了,因为终结器会将MyClass类型的对象提升到下一代GC生成,但我是否正确认为这是避免这种潜在内存泄漏的唯一方法?
答案 0 :(得分:4)
您的解决方案实际上不会解决问题,因为在收集对象之前不会调用终结器本身,并且正确识别TheObject
将通过事件处理程序使对象保持活动状态。
有两种可能的解决方法:
MyClass
实现IDisposable
并在Dispose
方法中取消注册事件处理程序。 C#具有using
语法来帮助使用类一个简单的实现方式是:
public interface ISubscription
{
bool IsAlive { get; }
void Fire();
}
public class Subscrition<T> : ISubscription
where T: class
{
public Subscrition(T target, Action<T> fire)
{
this.Target = new WeakReference<T>(target);
this.FireHandler = fire;
}
public WeakReference<T> Target { get; }
public Action<T> FireHandler { get; }
public bool IsAlive => this.Target.TryGetTarget(out var t);
public void Fire()
{
if (this.Target.TryGetTarget(out var target))
{
this.FireHandler(target);
}
}
}
public class WeakEvent
{
List<ISubscription> weakHandlers = new List<ISubscription>();
public void Register<T>(T target, Action<T> fire)
where T:class
{
this.Prune();
this.weakHandlers.Add(new Subscrition<T>(target, fire));
}
public void Unregister(ISubscription subscription)
{
this.Prune();
this.weakHandlers.Remove(subscription);
}
// Prune any dead handlers.
public void Prune()
{
this.weakHandlers.RemoveAll(_ => !_.IsAlive);
}
public void Fire()
{
this.Prune();
this.weakHandlers.ForEach(_ => _.Fire());
}
}
用法:
public class SomeObject
{
public WeakEvent WeakMyEvent = new WeakEvent();
}
public class MyClass
{
public SomeObject TheObject { get; }
public MyClass()
{
TheObject = new SomeObject();
TheObject.WeakMyEvent.Register(this, t => t.MyEventHandler());
}
private void MyEventHandler()
{
// some code
}
}
您还可以结帐this article以获得更复杂的实施
答案 1 :(得分:1)
你必须实现IDISPOSABLE,使用 - =来避免内存泄漏是无用的,因为你的对象总是活着的。 如果您的垃圾收集器无用,可以释放您的代表。 您在同一主题中有更多信息: Why and How to avoid Event Handler memory leaks?
答案 2 :(得分:0)
如果您的类实现了IDisposable接口,您可以取消订阅Dispose方法上的事件,然后您必须在实例上调用Dispose方法。这种方法的问题在于,大多数时候我们都不知道何时取消订阅(或何时调用Dispose),如果是这种情况,我建议您使用弱事件模式
弱事件模式使用从源对象到侦听器的弱引用,因此这种关系不会阻止GC收集这些对象。 WPF中的大多数控件都已实现弱事件模式。使用它的方法之一是通过通用的WeakEventManager类。
查看this article,查看一些使用弱事件模式的代码示例。