15

There are plenty of posts on speeding up reflection invokes, examples here:

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

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

and here:

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



My question is about speeding up generic invokes. Is this possible at all?

I've got an abstract class and a class which implements it...

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++; }
}

What I want to do is build up a list of these message handler classes and quickly invoke their HandleMessage()


At the moment, I'm doing something that's approximately this:

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

That's not the whole thing, but it's the important stuff...

The method.Invoke is very slow, I'd like to keep the generic parameters on the class, I realise I could lock this down to object and cast it in the HandleMessage method, but I'm trying to avoid doing this.

Is there anything I can do to speed this up? It's currently orders of magnitude slower than direct calls.

Any help would be appreciated.

Community
  • 1
  • 1
Rob
  • 1,687
  • 3
  • 22
  • 34

5 Answers5

9

Using Delegate.CreateDelegate() should be a lot faster. You will end up with a pointer to the real function, not a delegate that calls Invoke().

Try this:

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

In the same class add the following generic method. We invoke this dynamically above because we don't know the type parameter at compile time.

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

You then have a delegate that casts the parameter to the required type, then calls the HandleMessage method.

Tim Rogers
  • 21,297
  • 6
  • 52
  • 68
  • 1
    Beat me to it, but you should use the `MethodInfo` overload to match his question more closely. – Jonathan Dickinson Aug 25 '11 at 11:49
  • If you look at my answer you will see that his problem is a tiny bit more complicated than this (because of the generic parameters). – Jonathan Dickinson Aug 25 '11 at 12:18
  • Thanks Jonathan. I can see what the problem is. I've edited mine to show another solution that doesn't involve expressions. Hopefully, that one works. – Tim Rogers Aug 25 '11 at 13:01
  • `var paramType = handlerType.GetGenericArguments()[0];` is this line should be `var paramType = method.GetGenericArguments()[0];` ?@TimRogers – benlong Aug 26 '16 at 03:02
9

Are you using C# 4? If so, dynamic may speed things up:

Action<object> hook = message => ((dynamic)handler).HandleMessage((dynamic)message);
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • This appears to throw RuntimeBinderException: The best overloaded method match for 'X' has some invalid arguments. – Rob Aug 25 '11 at 11:50
  • @Rob: What about `((dynamic)handler).HandleMessage((dynamic)message)`? – Gabe Aug 25 '11 at 11:56
  • 1
    Yeah, it's much faster, it's not quite as fast as direct calls, but significantly faster than Invoke(). Thank you. I'm just evaluating the others people posted. – Rob Aug 25 '11 at 12:05
  • I've gone with this, it's very succinct, easy to maintain, and quick, possibly not as quick as other methods, but quick enough. Thank you all that posted especially Jonathan and Gabe. – Rob Aug 25 '11 at 12:52
  • 1
    Great answer, thanks! I've just used this method and in my case it was just as fast as direct calls. – dodgy_coder May 03 '12 at 05:27
6

You can use Delegate::CreateDelegate. This is significantly faster than 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()); 

Feel free to benchmark it, but I have benched it before and it was significantly faster.

Edit: I see your problem now, you can't do it the way I suggested.

You can use Expressions to compile a delegate that does the invoke for you, this will be very fast:

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: Apart from my Expression example above, the following will also work - and is what I do in these types of scenarios.

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!");
    }
}
Jonathan Dickinson
  • 9,050
  • 1
  • 37
  • 60
  • This appears to throw an ArgumentException : Error binding to target method. object does not appear to bind to the generic argument. – Rob Aug 25 '11 at 12:09
  • This appears to fail on the line: var lambda = Expression.Lambda>(ifExpr, instExpr, paramExpr); ---- ParameterExpression of type 'MyMessageType' cannot be used for delegate parameter of type 'Message' ---- That was your first edit btw. – Rob Aug 25 '11 at 12:37
  • @Rob - works fine here (if you use the classes I supplied); are your class samples exactly the same as the ones you are using in your code? Also did you modify the `var paramExpr` line? Looks like you have your answer anyway - but this does work if you copy it verbatim. – Jonathan Dickinson Aug 25 '11 at 13:19
  • Agreed, this is a valid solution. – Rob Aug 25 '11 at 15:41
  • Your last solution saved me from making generic methods at runtime and then invoking or delegating. Thank you! – AmmarCSE Aug 14 '14 at 14:16
1

If you know the signature, use Delegate.CreateDelegate.

If you don't know the signature, it's very tricky to get something that's fast. If you need speed, then whatever you do, try to avoid Delegate.DynamicInvoke which is extremely slow.

(Note that "slow" is very relative here. Make sure you really need to optimize this. DynamicInvoke is something like 2.5 million invocations per second (on my machine), which is very likely fast enough. The implementation below is more like 110+ million invocations per second and is faster than Method.Invoke.)

I found an article that discusses a way to do it (invoke a method fast without knowing the signature at compile time). Here is my version of the implementation. The weird issue is that you could build a lambda which represents the invocation, but you would not know the signature of that lambda and would have to call it dynamically (slowly). But instead, you can bake the strongly typed invocation into the lambda, with the lambda representing the act of invocation rather than the specific method itself. (The lambda ends up being a Func<object, object[], object> where you pass an object and some values and get back the return value.)

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;
}
Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
0

No, that is (sadly) not possible. Reflection is slow and MethodInfo.Invoke() is no exception. Couldn't you use (generic) Interfaces and thus direct calls?

Edit update: One thing comes to mind to really speed this up, but the coding overhead is massive: You can use dynamic code generation and compilation. That would mean dynamically building the source code that would call the method without reflection, dynamically compiling and executing this. This would mean an initially performance impact for creating and compiling the classes that do your work, but then you have direct calls for every subsequent calls.

Sebastian P.R. Gingter
  • 5,955
  • 3
  • 31
  • 73
  • Why is the coding overhead "massive"? I'd expect just a couple lines of code using `Expression`. – Gabe Aug 25 '11 at 11:49
  • First, when using expressions (or CodeDom) it is tidious in maintainability. Also debugging / validating the generated code is harder impossible. It is better to generate C# code on the fly and compiling this. This offers you better control over the generated control. – Sebastian P.R. Gingter Aug 25 '11 at 11:52