关于c#委托创建的性能表现

6

我的项目涉及了很多反射。因此,我将委托缓存到字典中。问题在于,我选择使用MethodInfo作为字典键,我尝试使用查找方法,就像这样:

Func<T,R> LookUp(Func<T,R> m)
{
  return (Func<T,R>)dict[m.Method];
}
//LookUp(MyCls.Method)

但是,经过一些测试,我发现使用函数地址喂给 LookUp 方法,也就是在动态创建过渡委托时,速度有点慢,确实非常慢:

class MyCls
{
    public static void Operate(int whatever){ }
}

class MainClass
{
    delegate void Doer<T>(T arg);
    static Dictionary<MethodInfo,Delegate> _dict = new Dictionary<MethodInfo,Delegate>();

public static void Main (string[] args)
    {
        Action<int> dg = MyCls.Operate;
        _dict[dg.Method] = Delegate.CreateDelegate(typeof(Action<int>),dg.Method);

        //performance test
        var start = Environment.TickCount;          
        for (int i = 0; i < 10000000; i++)
        {
            //LookUp(dg);//11               
            //LookUp<int>(MyCls.Operate);//1503
            //new MyCls();//431
        }

        Console.WriteLine (Environment.TickCount-start);
    }
    static  Action<T> LookUp<T>(Action<T> dg) 
    {
        //should return (Action<T>)_dict[dg.Method];
        return null;
    }

所以问题是:为了提高性能,我应该改变我的方法并编写一些不安全的代码(C#是否支持函数指针?),还是有其他基于C#的解决方案适用于这种情况?
请帮帮我!

我认为您需要提供更多信息。首先,我没有看到第一块代码和第二块代码之间有任何直接关系。您如何使用这个字典?此外,我假设每个操作旁边被注释的整数是它们完成所需的持续时间?如果传递方法组较慢,则只需传递 dg。 - Sean Thoman
抱歉,测试片段有点混乱。我已经重写了它以适应我的真实意图。但这都不重要。我的关注点是将函数地址传递给LookUp方法,因为每次调用时它都会自动创建一个委托,而委托的创建非常昂贵,我认为跳过这一步,直接使用函数地址和指针可能更好。此外,Type.GetMethod也很慢。也许我应该使用字符串或枚举作为缓存委托字典的键。 - Need4Steed
我真的不理解 LookUp 方法的意义。如果你已经有了要传递的 Func<T, R> 参数,为什么还需要查找它呢? - Sean Thoman
重点在于缓存。并非所有方法都对客户端透明(即设置器),其中许多方法是通过相应属性提供的名称引用的,而有些方法则对客户端可见。我尝试重载LookUp方法,以便可以以相同的方式处理它们。嗯,这个解决方案似乎有点愚蠢。看来我需要重新审视设计。 - Need4Steed
3个回答

1
一般来说,从性能的角度考虑,使用接口总是比使用委托/反射更好。
您是否可以控制对象以使用接口而不是委托?

1

我曾经在一个Winform应用程序中使用过一个类来进行事件聚合(中介者),该类具有以Type为键和Delegate为值的字典缓存。这个类看起来像...

public sealed class EventAggregator
    {
        #region Fields

        private readonly Dictionary<Type, List<Object>> subscribers = new Dictionary<Type, List<Object>>();

        #endregion

        #region Public Methods

        public void Subscribe<TMessage>(Action<TMessage> handler)
        {
            if (subscribers.ContainsKey(typeof(TMessage)))
            {
                var handlers = subscribers[typeof(TMessage)];
                handlers.Add(handler);
            }
            else
            {
                var handlers = new List<Object> {handler};
                subscribers[typeof(TMessage)] = handlers;
            }
        }

        public void Unsubscribe<TMessage>(Action<TMessage> handler)
        {
            if (subscribers.ContainsKey(typeof(TMessage)))
            {
                var handlers = subscribers[typeof(TMessage)];
                handlers.Remove(handler);

                if (handlers.Count == 0)
                {
                    subscribers.Remove(typeof(TMessage));
                }
            }
        }

        public void Publish<TMessage>(TMessage message)
        {
            if (subscribers.ContainsKey(typeof(TMessage)))
            {
                var handlers = subscribers[typeof(TMessage)];
                foreach (Action<TMessage> handler in handlers)
                {
                    handler.Invoke(message);
                }
            }
        }

        #endregion
    }

我猜这与你所尝试的相似。

不要在委托本身上查找,而是尝试在需要该委托的类型上查找。


0

委托是封装函数的引用类型,本质上它们是函数指针。

看起来你想要实现一种函数存储库。如果所有函数都将匹配 Func 的签名,则使用它,而不要使用通用的 Delegate 类。

或者,如果您想要存储具有不同签名的所有不同类型的委托,可以使用 Delegate 类,并且应该使用 .DynamicInvoke() 方法,而不是 MethodInfo 作为键,使用一些更简单的东西,例如字符串、函数返回值或两者的组合。

这里有一个简单的示例,展示了各种 LookUp 技术:

class FunctionRepository : List<Delegate> // could also be a Dictionary<,>
{
    public R Invoke<R>(string name, params object[] args)
    {
        var _delegate = this.Single(x => x.Method.ReturnType == typeof(R)
            && x.Method.Name == name);

        return (R)_delegate.DynamicInvoke(args);
    }  

    public Func<R> LookUp<R>(string name, params object[] args)
    { 
        var _delegate = this.Single(x => x.Method.ReturnType == typeof(R) 
            && x.Method.Name == name);

        return () => (R)_delegate.DynamicInvoke(args); 
    }

    public Func<Object[], R> LookUp<R>(string name)
    { 
        var _delegate = this.Single(x => x.Method.ReturnType == typeof(R) 
            && x.Method.Name == name);

        return (args) => (R)_delegate.DynamicInvoke(args); 
    }
}

1
动态调用非常慢,完全没有缓存的意义。我实际上将所有带参数和返回值的缓存函数都符合某个接口。因为装箱/拆箱比动态更便宜,将各种委托作为委托存储并不那么糟糕。无论如何,还是谢谢。 - Need4Steed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接