加速c# /. net的反射调用

本文关键字:反射 调用 net 加速 | 更新日期: 2023-09-27 18:06:41

有很多关于加速反射调用的帖子,例如:

在。net/c#中使用委托加速反射API

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

:

示例:在。net/c#中使用委托加速反射API



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

我有一个抽象类和一个实现它的类…

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());

这不是全部,但这是最重要的……

的方法。调用是非常缓慢的,我想保持类上的泛型参数,我意识到我可以将其锁定到对象并在HandleMessage方法中强制转换,但我试图避免这样做。

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

加速c# /. net的反射调用

你正在使用c# 4吗?如果是这样,dynamic可能会加快速度:

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

使用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方法。

您可以使用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()); 

请随意对其进行基准测试,但我以前测试过它,并且它明显更快。

:我现在明白你的问题了,你不能按我建议的方式做。

你可以使用表达式来编译一个委托来为你执行调用,这将是非常快:

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());

Edit:除了上面的表达式示例之外,下面也可以工作-这是我在这些类型的场景中所做的。

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!");
    }
}

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

如果你不知道签名,想得到快速的东西是非常棘手的。如果你需要速度,那么无论你做什么,尽量避免Delegate.DynamicInvoke,这是非常慢的。

(注意这里的"慢"是相对的。确保你真的需要优化它。DynamicInvoke大约是每秒250万次调用(在我的机器上),这可能已经足够快了。下面的实现更像是每秒1.1亿次调用,比Method.Invoke更快。)

我找到了一篇文章,讨论了一种方法(在编译时不知道签名的情况下快速调用方法)。这是我的实现版本。奇怪的问题是,您可以构建一个表示调用的lambda,但您不知道该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;
}

不,这(很遗憾)是不可能的。反射慢的,MethodInfo.Invoke()也不例外。难道你不能使用(通用)接口从而直接调用吗?

Edit update:有一件事可以真正加快这个速度,但编码开销很大:您可以使用动态代码生成和编译。这就意味着动态地构建调用该方法而不需要反射的源代码,动态地编译并执行它。这将意味着创建和编译执行您的工作的类最初的性能影响,但随后您可以直接调用每个后续调用。