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

时间:2017-11-21 13:22:01

标签: c# memory-leaks garbage-collection

假设我有一个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生成,但我是否正确认为这是避免这种潜在内存泄漏的唯一方法?

3 个答案:

答案 0 :(得分:4)

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

有两种可能的解决方法:

  1. 使MyClass实现IDisposable并在Dispose方法中取消注册事件处理程序。 C#具有using语法来帮助使用类
  2. 对事件使用弱引用,而不依赖于默认事件语法。
  3. 一个简单的实现方式是:

    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,查看一些使用弱事件模式的代码示例。