检测丢失事件取消订阅的最快方法是什么?

时间:2015-04-07 19:10:21

标签: c# mono

我们的应用程序远远超出了快速的设计改进,并且处于一切正常运行的状态,但存在内存泄漏。我们不能使用任何流行的分析器,因为我们在一些旧版Mono中将Unity3D引擎内部沙箱化。

我们有许多临时对象,其中有很多事件正在发挥作用,我们怀疑一些糟糕的设计和丢失的事件取消订阅可以在这里发挥作用。

我很想知道是否有人能想出一些简单的方法来扫描项目中是否存在丢失的事件取消订阅。我希望可以使用Visual Studio本身编写一个可以查看事件的脚本,并告诉我们-= and +=是否在项目中同等存在。

1 个答案:

答案 0 :(得分:0)

在代码中计数 - =和+ =非常容易出错,因为事件相关的内存泄漏通常是由某些条件执行引起的。

如果您缺少剖析器,您可以使用Reflection在任何给定时间使用以下内容枚举任何对象的类字段事件订阅者:

public static class InvocationHelper
{
    public static Dictionary<string,Delegate[]> GetInvocationList<TClass>(TClass source)
    {
        if (null == source)
            return new Dictionary<string, Delegate[]>();

        var retval = new Dictionary<string,Delegate[]>();

        var members = typeof (TClass).GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).
            Where(t=>t.MemberType == MemberTypes.Event).ToArray();

        foreach (var memberInfo in members)
        {
            var field = typeof (TClass).GetField(memberInfo.Name,
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            if (field == null) 
                continue;

            var delegateField = field.GetValue(source) as Delegate;

            if(null == delegateField)
                continue;

            retval[memberInfo.Name] = delegateField.GetInvocationList();                
        }

        return retval;
    }
}

上面的方法遍历source中的所有事件,并返回带有遇到事件的调用列表的字典。

这仅适用于类似字段的事件(对于您可以手动计算订阅者的自定义事件)。

例如,假设您有一些类具有字段的事件SomeEvent

public class SomeEventHolder
{
    public delegate void SomeEventHandler(bool par);

    public event SomeEventHandler SomeEvent;

    protected virtual void OnSomeEvent(bool par)
    {
        var handler = SomeEvent;
        if (handler != null) handler(par);
    }
}

您创建此类的实例并订阅三次SomeEvent但仅取消订阅一次:

var instance = new SomeEventHolder();

//....

instance.SomeEvent += inst_SomeEvent;
instance.SomeEvent += inst_SomeOther;
instance.SomeEvent += inst_SomeEvent;
instance.SomeEvent -= inst_SomeEvent;

然后,您可以为GetInvocationList调用instance并让订阅者离开,然后将他们的列表输出到控制台:

var invocationList = InvocationHelper.GetInvocationList(instance);

foreach (var item in invocationList)
{
    Console.WriteLine("Subscribers for {0} : {1}",
        item.Key,
        string.Join(Environment.NewLine, item.Value.Select(s => s.Method.ToString())));
}

这会导致

Subscribers for SomeEvent : Void inst_SomeEvent(Boolean)
Void inst_SomeOther(Boolean)