简体   繁体   中英

How do I add a method to ANY event's invocation list at runtime (C# + Unity)

I'm trying to create an event-based sound triggering system for Unity (5.x) built on top of a project where existing code should be modified as little as possible.

The existing project has all kinds of events with different delegate signatures, like so:

public delegate void CustomDelegateType1();
public event CustomDelegateType1 OnCustomEvent1;

public delegate void CustomDelegateType2(CustomClass param);
public event CustomDelegateType2 OnCustomEvent2;

I want to use these events to trigger sounds by adding a method to the invocation list at runtime, treating the new method just like any other subscriber. Basically, start by using reflection to get a list of events on the current GameObject , display those events in an editor dropdown on my Component , and then bind a method to that event on Awake()

The problem is: how do you add a generic method to ANY event type (as long as it returns void), given the different number and types of params used in the different delegates?

[Edit: this also needs to run on mobile devices, and hence Reflection.Emit usage isn't viable]

It seems that you only want to register to certain events (since you want to choose in the UI. Why don't you juste create a classe that manages your audio and that registers to all the events you need.

Then you could create different methods that matches all the needed delegates.

I don't really see why you would need to add the method only at runtime since you plan to do it on Awake, it means you plan to know all the needed methods anyway

I was able to adapt the code presented in this SO answer to solve my problem:

    public static Delegate AddHandler(this object obj, string eventName, Action action)
{
    // Filter list by event name
    EventInfo ev = obj.GetType().GetEvents().Where(x => x.Name == eventName).FirstOrDefault();
    if (ev == null)
    {
        Debug.LogWarning(eventName + " not found on " + obj.ToString());
        return null;
    }

    // Simple case - the signature matches so just add the new handler to the list
    if (ev.EventHandlerType == typeof(Action))
    {
        ev.AddEventHandler(obj, action);

        return action;
    }

    Delegate del = CreateDelegate(ev, action);
    ev.AddEventHandler(obj, del);

    return del;
}

public static void RemoveHandler(this object obj, string eventName, Action action)
{
    // Filter list by event name
    var ev = obj.GetType().GetEvents().Where(x => x.Name == eventName).FirstOrDefault();
    if (ev == null)
    {
        Debug.LogWarning(eventName + " not found on " + obj.ToString());
        return;
    }

    // Simple case - the signature matches so just add the new handler to the list
    if (ev.EventHandlerType == typeof(Action))
    {
        ev.RemoveEventHandler(obj, action);
    }
    else
    {
        Delegate del = CreateDelegate(ev, action);
        ev.RemoveEventHandler(obj, del);
    }
}

private static Delegate CreateDelegate(EventInfo ev, Action action)
{
    // Retrieve the parameter types of the event handler
    var parameters = ev.EventHandlerType.GetMethod("Invoke").GetParameters();

    ParameterExpression[] parameters2 = Array.ConvertAll(parameters, x => Expression.Parameter(x.ParameterType, x.Name));
    MethodCallExpression call;

    // We are "opening" the delegate and directly using the Target and the Method.
    if (action.Target == null) // Event is static
        call = Expression.Call(action.Method);
    else // Event is instanced
        call = Expression.Call(Expression.Constant(action.Target), action.Method);

    var exp = Expression.Lambda(ev.EventHandlerType, call, parameters2);

    return exp.Compile();
}

Unfortunately I couldn't optimize this method to be efficient enough for production usage, as it causes a massive hit on init() execution time, so I ended up writing a bunch of audio helper classes instead.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM