grapefrukt
grapefrukt

Reputation: 27045

Faster calling of methods using reflection

I'm porting over some old code from AS3 (via Haxe) to C#.
Parts of the code are transpiled, others I have manually rewritten in C#. One of these parts is the event dispatching.

I have event listeners registering to an event dispatcher, all listeners have a signature like this:

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

The events keep a small amount of data and a type:

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

I keep a list keyed on the particular event type (a string, due to legacy reasons), once an event is dispatched I find the appropriate handlers keyed with that string and call the them with the event data.

I am currently using reflection to do this, but it is proving to be prohibitively slow. I have found several threads that share this problem.

My particular issue is that the method signature varies, if it was always an Event as a parameter I could use the solutions provided, but alas.

I'd be fine with trading some memory/time at setup to get subsequent calls to be faster. I can get a reference to the method and work out what type it expects, but I'm unsure how to store and call this later?

Upvotes: 0

Views: 1109

Answers (1)

pakeha_by
pakeha_by

Reputation: 2244

You can create and compile LINQ expression for each handler method and cache it for future use.

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

Once you have hander instance, you can call its method via compiled delegate:

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

You can pre-generate CompiledDelegate for each of handlers and add to the dispatch table together with handlers themselves.

Calling method via compiled delegate (once it's compiled of course) is approximately 10 times faster then calling same method via reflection.

Upvotes: 1

Related Questions