C#:如何避免这种潜在的内存泄漏

use*_*248 4 c# garbage-collection memory-leaks

假设我有一个 C# 类,如下所示:

public class MyClass {
    public SomeObject TheObject { get; }

    public MyClass() {
        TheObject = new SomeObject();
        TheObject.MyEvent += MyEventHandler;
    }

    private void MyEventHandler() {
        // some code
    }
}
Run Code Online (Sandbox Code Playgroud)

该类创建一个名为 TheObject 的 SomeObject 类型的内部对象,并向该对象上的事件添加一个事件处理程序。

由于 TheObject 是一个公共属性,这意味着任何其他代码段都可以维护指向该对象的指针;反过来,这将使 MyClass 类型的对象保持活动状态,因为 TheObject 有一个以事件处理程序形式指向 MyClass 的指针。

因此,我认为保持此代码不受此事件影响的唯一方法是向 MyClass 添加终结器:

public ~MyClass() {
    TheObject?.MyEvent -= MyEventHandler;
}
Run Code Online (Sandbox Code Playgroud)

这太糟糕了,因为终结器会将 MyClass 类型的对象提升到下一代 GC,但我是否正确地认为这是避免这种潜在内存泄漏的唯一方法?

Tit*_*mir 5

您的解决方案实际上不会解决问题,因为在可以收集对象之前不会调用终结器本身,并且正如您正确识别的那样,TheObject将通过事件处理程序使对象保持活动状态。

有两个潜在的修复:

  1. 使在方法中MyClass实现IDisposable和注销事件处理程序Dispose。C# 具有using帮助使用类的语法
  2. 对事件使用弱引用,而不是依赖于默认的事件语法。

一个简单的实现是:

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());

    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

您还可以查看这篇文章以获得更复杂的实现