2011-08-25 70 views
15

有很多的职位上加快反射调用,例子在这里:加快反射调用C#/。NET

Speeding up Reflection API with delegate in .NET/C#

https://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/

这里:

Example : Speeding up Reflection API with delegate in .NET/C#



我的问题是关于加速泛型调用。这可能吗?

我有一个抽象类,它实现它的类...

public abstract class EncasulatedMessageHandler<T> where T : Message 
{ 
    public abstract void HandleMessage(T message); 
} 

public class Handler : EncasulatedMessageHandler<MyMessageType> 
{ 
    public int blat = 0; 
    public override void HandleMessage(MyMessageType message) { blat++; } 
} 

我想要做的就是建立这些消息处理程序类的列表,并迅速调用它们的handleMessage(什么)


目前,我正在做的事情就是大约是:

object handler = Activator.CreateInstance(typeof(Handler)); // Ignore this, this is done up front. 

MethodInfo method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public); 

Action<object> hook = new Action<object>(delegate(object message) 
{ 
    method.Invoke(handler, new object[] { message }); 
}); 

// Then when I want to invoke it: 

hook(new MyMessageType()); 

这并不是整个事情,但它是最重要的东西......

的method.Invoke很慢,我想保持在类的泛型参数,我知道我可以锁定这个下降到对象并将其转换为HandleMessage方法,但我试图避免这样做。

我能做些什么来加快速度?目前比直接呼叫慢几个数量级。

任何帮助,将不胜感激。

回答

7

您使用的是C#4吗?如果是这样,dynamic可以加快速度:

Action<object> hook = message => ((dynamic)handler).HandleMessage((dynamic)message); 
+0

这似乎是抛出RuntimeBinderException:'X'的最佳重载方法匹配有一些无效的参数。 – Rob

+0

@Rob:'((动态)处理程序).HandleMessage((动态)消息)''? – Gabe

+0

工作! :) Woot! – Rob

0

不,这是(可惜)不可能。反射慢,MethodInfo.Invoke()也不例外。你不能使用(通用)接口并直接调用吗?

编辑更新:想到真正加快速度,但编码开销很大:您可以使用动态代码生成和编译。这意味着动态构建可以不经反射调用该方法的源代码,动态编译并执行该代码。这意味着创建和编译执行您的工作的类的初始性能影响,但是您可以直接调用每个后续调用。

+0

它*是*可能的。 –

+0

为什么编码开销“巨大”?我只希望看到几行使用'Expression'的代码。 – Gabe

+0

首先,当使用表达式(或CodeDom)时,它在可维护性方面很不好。另外调试/验证生成的代码更加困难。最好是即时生成C#代码并编译它。这样可以更好地控制生成的控件。 –

8

使用Delegate.CreateDelegate()应该快很多。你最终会得到一个指向真实函数的指针,而不是调用Invoke()的代理。

试试这个:

object handler = Activator.CreateInstance(typeof(Handler)); 
var handlerType = handler.GetType(); 
var method = handlerType.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public); 
var paramType = handlerType.GetGenericArguments()[0]; 

// invoke the MakeHandleMessageDelegate method dynamically with paramType as the type parameter 
// NB we're only doing this once 
Action<object> hook = (Action<object>) this.GetType().GetMethod("MakeHandleMessageDelegate") 
      .MakeGenericMethod(paramType) 
      .Invoke(null, new [] { handler }); 

在同一个类中添加下面的泛型方法。我们在上面动态调用它,因为我们在编译时不知道类型参数。

public static Action<object> MakeHandleMessageDelegate<T>(object target) 
{ 
    var d = (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), target, "HandleMessage"); 

    // wrap the delegate another that simply casts the object parameter to the required type 
    return param => d((T)param); 
} 

然后,您有注塑参数需要的类型的委托,然后调用HandleMessage方法。

+1

打败我,但你应该使用'MethodInfo'重载来更紧密地匹配他的问题。 –

+0

如果你看看我的答案,你会发现他的问题比这个复杂得多(因为泛型参数)。 –

+0

谢谢乔纳森。我可以看到问题是什么。我编辑了我的内容以显示另一个不涉及表达式的解决方案。希望那个工作。 –

6

您可以使用Delegate::CreateDelegate。这比Invoke()快得多。

var handler = Activator.CreateInstance(typeof(Handler)); 
var method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public); 
var hook = (Action<object>)Delegate.CreateDelegate(typeof(Action<object>), handler, method); 

// Then when you want to invoke it: 
hook(new MyMessageType()); 

随意基准,但我以前板凳它,它是显著更快。

编辑:我现在看到你的问题,你不能按照我的建议。

您可以使用表达式来编译的委托,做的invoke对你来说,这将是非常快:

var type = typeof(Handler); 
var instance = Activator.CreateInstance(type); 
var method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public); 

var originalType = type; 
// Loop until we hit the type we want. 
while (!(type.IsGenericType) || type.GetGenericTypeDefinition() != typeof(EncasulatedMessageHandler<>)) 
{ 
    type = type.BaseType; 
    if(type == null) 
     throw new ArgumentOutOfRangeException("type"); 
} 

var messageType = type.GetGenericArguments()[0]; // MyMessageType 

// Use expression to create a method we can. 
var instExpr = Expression.Parameter(typeof(object), "instance"); 
var paramExpr = Expression.Parameter(typeof(Message), "message"); 
// (Handler)instance; 
var instCastExpr = Expression.Convert(instExpr, originalType); 
// (MyMessageType)message 
var castExpr = Expression.Convert(paramExpr, messageType); 
// ((Handler)inst).HandleMessage((MyMessageType)message) 
var invokeExpr = Expression.Call(instCastExpr, method, castExpr); 
// if(message is MyMessageType) ((Handler)inst).HandleMessage((MyMessageType)message); 
var ifExpr = Expression.IfThen(Expression.TypeIs(paramExpr, messageType), invokeExpr); 

// (inst, message) = { if(message is MyMessageType) ((Handler)inst).HandleMessage((MyMessageType)message); } 
var lambda = Expression.Lambda<Action<object, Message>>(ifExpr, instExpr, paramExpr); 
var compiled = lambda.Compile(); 
Action<Message> hook = x => compiled(instance, x); 

hook(new MyMessageType()); 

编辑:除了上面我的表情例如,下面还将努力 - 这是我在这些类型的场景中所做的。

var instance = (IEncapsulatedMessageHandler)Activator.CreateInstance(typeof(Handler)); 
instance.HandleMessage(new MyMessageType()); 

public class Message { } 

public class MyMessageType : Message { } 

public interface IEncapsulatedMessageHandler 
{ 
    void HandleMessage(Message message); 
} 

public abstract class EncasulatedMessageHandler<T> : IEncapsulatedMessageHandler where T : Message 
{ 
    public abstract void HandleMessage(T message); 

    void IEncapsulatedMessageHandler.HandleMessage(Message message) 
    { 
     var msg = message as T; 
     if (msg != null) 
      HandleMessage(msg); 
    } 
} 

public class Handler : EncasulatedMessageHandler<MyMessageType> 
{ 
    public override void HandleMessage(MyMessageType message) 
    { 
     Console.WriteLine("Yo!"); 
    } 
} 
+0

这看起来会引发一个ArgumentException:将错误绑定到目标方法。对象似乎不绑定到泛型参数。 – Rob

+0

@Rob看看编辑,我现在看到你的问题。 –

+0

这看起来失败了:var lambda = Expression.Lambda >(ifExpr,instExpr,paramExpr); ----'MyMessageType'类型的ParameterExpression不能用于'Message'类型的委托参数----这是你第一次编辑btw。 – Rob

0

如果您知道签名,请使用Delegate.CreateDelegate

如果您不知道签名,那么获取速度非常快。如果你需要速度,那么无论你做什么,尽量避免Delegate.DynamicInvoke这是非常缓慢的。 (请注意,“slow”在这里是非常相对的,确保你真的需要优化这个,DynamicInvoke大约是每秒250万次调用(在我的机器上),这很可能足够快。每秒更像110+亿调用和比Method.Invoke更快。)

我发现an article,讨论的方式来做到这一点(快速调用一个方法,不知道在编译时的签名)。这是我的版本的实施。奇怪的问题是你可以建立一个表示调用的lambda表达式,但是你不知道那个lambda表达式的签名,并且必须动态地(缓慢地)调用它。但是相反,您可以将强类型的调用烘焙到lambda中,其中lambda表示调用的行为而不是特定的方法本身。 (λ最后是一个Func<object, object[], object>,你传递一个对象和一些值并取回返回值。)

public static Func<object, object[], object> ToFastLambdaInvocationWithCache(
    this MethodInfo pMethodInfo 
) { 
    Func<object, object[], object> cached; 
    if (sLambdaExpressionsByMethodInfoCache.TryGetValue(pMethodInfo, out cached)) 
     return cached; 

    var instanceParameterExpression = Expression.Parameter(typeof(object), "instance"); 
    var argumentsParameterExpression = Expression.Parameter(typeof(object[]), "args"); 

    var index = 0; 
    var argumentExtractionExpressions = 
     pMethodInfo 
     .GetParameters() 
     .Select(parameter => 
     Expression.Convert(
      Expression.ArrayAccess(
       argumentsParameterExpression, 
       Expression.Constant(index++) 
      ), 
      parameter.ParameterType 
     ) 
    ).ToList(); 

    var callExpression = pMethodInfo.IsStatic 
     ? Expression.Call(pMethodInfo, argumentExtractionExpressions) 
     : Expression.Call(
     Expression.Convert(
      instanceParameterExpression, 
      pMethodInfo.DeclaringType 
     ), 
     pMethodInfo, 
     argumentExtractionExpressions 
    ); 

    var endLabel = Expression.Label(typeof(object)); 
    var finalExpression = pMethodInfo.ReturnType == typeof(void) 
     ? (Expression)Expression.Block(
      callExpression, 
      Expression.Return(endLabel, Expression.Constant(null)), 
      Expression.Label(endLabel, Expression.Constant(null)) 
     ) 
     : Expression.Convert(callExpression, typeof(object)); 

    var lambdaExpression = Expression.Lambda<Func<object, object[], object>>(
     finalExpression, 
     instanceParameterExpression, 
     argumentsParameterExpression 
    ); 
    var compiledLambda = lambdaExpression.Compile(); 
    sLambdaExpressionsByMethodInfoCache.AddOrReplace(pMethodInfo, compiledLambda); 
    return compiledLambda; 
}