0

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?

grapefrukt
  • 27,016
  • 6
  • 49
  • 73
  • Reflection.Emit might help in this case, if it's just a function call then it should be fairly easy. Alternatively, if you have a small number of cases, you might store in that dictionary your own delegate (that you create when populating the dictionary) which accepts a generic parameter and casts it as appropriate before calling the listener(yes, it adds even another layer but it pretty fast compared to _pure_ reflection). – Adriano Repetti May 05 '20 at 08:46
  • Porting this to the on-board [Event System](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/) in dotnet is not an option? ( Or maybe you can get "inspired by" it ... ) – Fildor May 05 '20 at 08:49
  • @Fildor sadly this is far too deeply embedded in the legacy code to do changes like that – grapefrukt May 05 '20 at 09:03
  • @grapefrukt That's too bad, but I somewhat expected something like that. Anyways - maybe have a look into it to get inspired how they do it. I guess any part that can be done without reflection should improve performance ... – Fildor May 05 '20 at 09:06
  • I'd then second Adriano's suggestion to look into [multicast](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/how-to-combine-delegates-multicast-delegates) [delegates](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/) – Fildor May 05 '20 at 09:12
  • i suppose you could build your delegates with LINQ Expressions and cache them per handler type? – timur May 05 '20 at 09:21

1 Answers1

1

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.

pakeha_by
  • 2,081
  • 1
  • 15
  • 7