【问题标题】:Faster calling of methods using reflection使用反射更快地调用方法
【发布时间】:2020-05-05 19:12:17
【问题描述】:

我正在将一些旧代码从 AS3(通过 Haxe)移植到 C#。
部分代码已转译,其他部分我用 C# 手动重写。这些部分之一是事件分派。

我有事件监听器注册到事件调度器,所有监听器都有这样的签名:

public void handleSomething(Event e)
// they may also use a subclass of Event as a parameter
public void handleAnother(MouseEvent e)

事件保留少量数据和类型:

public class Event {

    public const string ENTER_FRAME = "enter_frame";
    public const string RESIZE = "resize";
    public const string CHANGE = "change";

    readonly string type;

    public Event(string type) {
        this.type = type;
    }
}

我保留一个以特定事件类型(字符串,由于遗留原因)为键的列表,一旦事件被调度,我会找到以该字符串为键的适当处理程序并使用事件数据调用它们。

我目前正在使用反射来执行此操作,但事实证明它非常缓慢。我发现 several threads 共享这个问题。

我的特殊问题是方法签名会有所不同,如果它始终是 Event 作为参数,我可以使用提供的解决方案,但是唉。

我可以在设置时交易一些内存/时间以使后续调用更快。我可以获得对该方法的引用并计算出它期望的类型,但我不确定以后如何存储和调用它?

【问题讨论】:

  • Reflection.Emit 在这种情况下可能会有所帮助,如果它只是一个函数调用,那么它应该相当容易。或者,如果您有少量案例,您可以在该字典中存储您自己的委托(您在填充字典时创建),它接受一个通用参数并在调用侦听器之前将其转换为适当的(是的,它甚至添加了另一个层,但与 pure 反射相比它相当快)。
  • 将其移植到 dotnet 中的板载 Event System 不是一种选择? (或者也许你可以从中得到“启发”……)
  • @Fildor 遗憾的是,这在遗留代码中嵌入得太深了,无法进行这样的更改
  • @grapefrukt 这太糟糕了,但我有点期待这样的事情。无论如何 - 也许看看它以获得灵感他们是如何做到的。我想任何可以在没有反射的情况下完成的部分都应该提高性能......
  • 我会同意阿德里亚诺的建议,调查multicastdelegates

标签: c# reflection


【解决方案1】:

您可以为每个处理程序方法创建和编译 LINQ 表达式并将其缓存以供将来使用。

public class CompiledDelegate
{
    // Assume that there is one one method per handler class type, add method name to dictionary key if necessary
    private static Dictionary<Type, CompiledDelegate> _Cache = new Dictionary<Type, CompiledDelegate>();

    public static CompiledDelegate Get(Type handlerType, string methodName)
    {
        CompiledDelegate result;
        if (!_Cache.TryGetValue(handlerType, out result))
        {
            var method = handlerType.GetMethod(methodName);

            // determine type of single method parameter
            var paramType = method.GetParameters().Single().ParameterType;

            // create expression tree (object h, object p) => ((handlerType)h).MethodName((paramType)p)
            var exprHandler = Expression.Parameter(typeof(object), "h");
            var exprParam = Expression.Parameter(typeof(object), "p");

            var lambda = Expression.Lambda(
                Expression.Call(
                    Expression.TypeAs(exprHandler, handlerType),  // instance, cast object to handlerType
                    method,                                       // method
                    Expression.TypeAs(exprParam, paramType)       // parameter, cast object to paramType
                ),
                exprHandler, exprParam                            // lamda params
            );

            result = new CompiledDelegate()
            {
                Method = method,
                // compile expression
                Compiled = (Action<object, object>)lambda.Compile()
            };

            _Cache.Add(handlerType, result);
        }

        return result;
    }

    public MethodInfo Method { get; private set; }
    public Action<object, object> Compiled { get; set; }
}

一旦你有了hander实例,你就可以通过编译的委托调用它的方法:

CompiledDelegate.Get(handler.GetType(), "handlerSomething").Compiled(handler, mouseEvent)

您可以为每个处理程序预先生成 CompiledDelegate 并与处理程序本身一起添加到调度表中。

通过编译的委托调用方法(当然是编译后)比通过反射调用相同的方法快大约 10 倍。

【讨论】:

  • 请注意:该字典不是线程安全的,因此该方法不是线程安全的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-08
相关资源
最近更新 更多