0

I have the below in my controller and would like to pass x, y, and z of type float to subscribers. I'm having some difficulties catching on however. What would I have to adjust to allow me to pass my floats (x, y, z) as parameters? Thank you.

private void ProcessEvents()
{
    if(Input.GetMouseButtonDown(1))
    {
        // Data to be passed to subscribers
        float x = Input.mousePosition.x;
        float y = Input.mousePosition.y;
        float z = Input.mousePosition.z;

        var publisher  = new EventPublisher();
        var handler = new Handler();

        // Void delegate with one parameter
        string eventName = "RightClickEvent";
        var rightClickEvent = publisher.GetType().GetEvent(eventName);

        rightClickEvent.AddEventHandler(publisher, EventProxy.Create<int>(rightClickEvent, i=>Debug.LogError(i + "!")));

        publisher.PublishEvents();
    }
}

Other sources:

public class ExampleEventArgs : EventArgs
{
    public int IntArg {get; set;}
}

Event Publisher:

public class EventPublisher
{
    public event EventHandler<ExampleEventArgs> RightClickEvent;

    /// <summary>
    /// Publishes the events.
    /// </summary>
    public void PublishEvents()
    {
        if(RightClickEvent != null) 
        {
            RightClickEvent(this, new ExampleEventArgs{IntArg = 5});
        }
    }
}

Handler:

public class Handler
{
    public void HandleEventWithArg(int arg) { Debug.LogError("Arg: " + string.Format("[{0}]", arg));    }
}

EventProxy:

static class EventProxy
{ 
    // Void delegate with one parameter
    static public Delegate Create<T>(EventInfo evt, Action<T> d)
    {
        var handlerType = evt.EventHandlerType;
        var eventParams = handlerType.GetMethod("Invoke").GetParameters();

        //lambda: (object x0, ExampleEventArgs x1) => d(x1.IntArg)
        var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x")).ToArray();
        var arg    = getArgExpression(parameters[1], typeof(T));
        var body   = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"), arg);
        var lambda = Expression.Lambda(body,parameters);
        return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
    }

    // Returns an expression that represents an argument to be passed to the delegate
    static Expression getArgExpression(ParameterExpression eventArgs, Type handlerArgType)
    {
        if(eventArgs.Type == typeof(ExampleEventArgs) && handlerArgType == typeof(int))
        {
            //"x1.IntArg"
            var memberInfo = eventArgs.Type.GetMember("IntArg")[0];
            return Expression.MakeMemberAccess(eventArgs,memberInfo);
        }
        throw new NotSupportedException(eventArgs + "->" + handlerArgType);
    }

    // Void delegates with no parameters
    static public Delegate Create(EventInfo evt, Action d)
    { 
        var handlerType = evt.EventHandlerType;
        var eventParams = handlerType.GetMethod("Invoke").GetParameters();

        //lambda: (object x0, EventArgs x1) => d()
        var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x"));
        var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"));
        var lambda = Expression.Lambda(body,parameters.ToArray());
        return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
    }
}
Nestor Ledon
  • 1,925
  • 2
  • 23
  • 36

2 Answers2

1

I have added some code that will achieve what u require:

I modified the getArgExpression function to this

// Returns a List of expressions that represent the arguments to be passed to the delegate
static IEnumerable<Expression> getArgExpression(ParameterExpression eventArgs, Type handlerArgType)
{
    if (eventArgs.Type == typeof(ExampleEventArgs) && handlerArgType == typeof(int))
    {
        //"x1.IntArg"
        var memberInfo = eventArgs.Type.GetMember("IntArg")[0];
        return new List<Expression> { Expression.MakeMemberAccess(eventArgs, memberInfo) };
    }
    if (eventArgs.Type == typeof(MousePositionEventArgs) && handlerArgType == typeof(float))
    {
        //"x1.X"
        var xMemberInfo = eventArgs.Type.GetMember("X")[0];
        //"x1.Y"
        var yMemberInfo = eventArgs.Type.GetMember("Y")[0];
        //"x1.Z"
        var zMemberInfo = eventArgs.Type.GetMember("Z")[0];
        return new List<Expression>
                   {
                       Expression.MakeMemberAccess(eventArgs, xMemberInfo),
                       Expression.MakeMemberAccess(eventArgs, yMemberInfo),
                       Expression.MakeMemberAccess(eventArgs, zMemberInfo),
                   };
    }
    throw new NotSupportedException(eventArgs + "->" + handlerArgType);
}

The calling function stays the same, because it has an overload for IEnumerable<Expression>. I then added the EventArgs specific to your MousePosition situation

public class MousePositionEventArgs : EventArgs
{
    public float X { get; set; }
    public float Y { get; set; }
    public float Z { get; set; }
}

Further more i wrote a new function in 'EventProxy' that will handle a delegate with 3 parameters of the same type

// Void delegate with three parameters
static public Delegate Create<T>(EventInfo eventInformation, Action<T, T, T> lambdaDelegate)
{
    var handlerType = eventInformation.EventHandlerType;
    var eventParams = handlerType.GetMethod("Invoke").GetParameters();

    //lambda: (object x0, ExampleEventArgs x1) => d(x1.X,x1.Y,x1.Z)
    var parameters = eventParams.Select(p => Expression.Parameter(p.ParameterType, "x")).ToArray();
    var arg = getArgExpression(parameters[1], typeof(T));
    var body = Expression.Call(Expression.Constant(lambdaDelegate), lambdaDelegate.GetType().GetMethod("Invoke"), arg);
    var lambda = Expression.Lambda(body, parameters);
    return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
}

Now we can easily add a MouseEvent subscription by using the following code

rightClickEvent.AddEventHandler(publisher, EventProxy.Create<float>(rightClickEvent, (t, u, v) => Console.Write(t + "x" + u + "x" + v + "!")));

Design-wise this solution is not the best, it uses the current design you have shown, but i do have strong reservations as to maintaining a scalable and easy to follow architecture with this approach. I would suggest you create an adapter/converter that will generate the lambda parameters from the EventArgs class, they can be easily specified in the EventArgs by implementing an interface, that way the getArgExpression will only use the converter to generate the appropriate parameters, and you can get rid of the many if (Type == typeof(...)).

It will take me some more time to draw up that solution, but if you are really interested, i can try to write it down, and post it.

matei.navidad
  • 676
  • 6
  • 18
  • Thank you greatly for your robust response. I am currently trying my best to improve my event implementation and would greatly appreciate if could show me a better solution. Thank you. – Nestor Ledon Mar 25 '13 at 21:15
  • While searching online, i see that your code snippet was taken from http://stackoverflow.com/questions/45779/c-sharp-dynamic-event-subscription , this implementation required that you could register an event handler by using the eventname. If you could give me a few more details regarding what exactly you are trying to achieve, i could come up with a solution that could better suit your needs. Do you need to raise and handle only the MouseEventArgs or do you require a solution for multiple, generic scenarios? – matei.navidad Mar 25 '13 at 21:38
  • That is indeed the snippet. My goal is to create a generic solution that will accept parameters of similar types for multiple cases. MouseEventArgs and WorldPositionArgs for example would both take X,Y,Z floats. Hope that helps and thank you so much again for your assistance. – Nestor Ledon Mar 25 '13 at 21:45
  • To briefly explain what I'm trying to do: My goal is to keep each Feature of my application decoupled. Features are built in an MVC like pattern with their Controllers acting as the entry point. A Features Controller is responsible for subscribing to the main application events. Ex: I'm adding a Highlight feature to my MainApplication. Subscriptions to events happen in MainApplicationController. HighlightController registers to the "RightClickEvent" in MainApplicationController. HighlightController now receives parameters passed from the RightClickEvent. Hope it helps, and thanks again. – Nestor Ledon Mar 26 '13 at 05:50
  • Just so you know, i haven't forgotten about this, i'm gonna write up an answer as soon as i can, this weekend - the latest. – matei.navidad Mar 26 '13 at 22:17
  • Might have sound something close to what I need: http://www.buunguyen.net/blog/revisit-dynamic-event-handler-in-net.html – Nestor Ledon Mar 27 '13 at 05:15
1

Ok, so i read what you said about decoupling application modules in MVC-like style. I normally like to work with strong typed code, even when using reflection, but i'm relatively new to MVC, and don't know the recommended practices. You know your requirements better than i do, so i simply edited Nguyen's solution, because i believe he was using some extensions that were not included in the file, and posted the result here. All credit goes to Nguyen.

namespace Dynamics
{
    public static class DynamicHandler
    {
        /// <summary>
        /// Invokes a static delegate using supplied parameters.
        /// </summary>
        /// <param name="targetType">The type where the delegate belongs to.</param>
        /// <param name="delegateName">The field name of the delegate.</param>
        /// <param name="parameters">The parameters used to invoke the delegate.</param>
        /// <returns>The return value of the invocation.</returns>
        public static object InvokeDelegate(this Type targetType, string delegateName, params object[] parameters)
        {
            return ((Delegate)targetType.GetField(delegateName).GetValue(null)).DynamicInvoke(parameters);
        }

        /// <summary>
        /// Invokes an instance delegate using supplied parameters.
        /// </summary>
        /// <param name="target">The object where the delegate belongs to.</param>
        /// <param name="delegateName">The field name of the delegate.</param>
        /// <param name="parameters">The parameters used to invoke the delegate.</param>
        /// <returns>The return value of the invocation.</returns>
        public static object InvokeDelegate(this object target, string delegateName, params object[] parameters)
        {
            return ((Delegate)target.GetType().GetField(delegateName).GetValue(target)).DynamicInvoke(parameters);
        }

        /// <summary>
        /// Adds a dynamic handler for a static delegate.
        /// </summary>
        /// <param name="targetType">The type where the delegate belongs to.</param>
        /// <param name="fieldName">The field name of the delegate.</param>
        /// <param name="func">The function which will be invoked whenever the delegate is invoked.</param>
        /// <returns>The return value of the invocation.</returns>
        public static Type AddHandler(this Type targetType, string fieldName,
            Func<object[], object> func)
        {
            return InternalAddHandler(targetType, fieldName, func, null, false);
        }

        /// <summary>
        /// Adds a dynamic handler for an instance delegate.
        /// </summary>
        /// <param name="target">The object where the delegate belongs to.</param>
        /// <param name="fieldName">The field name of the delegate.</param>
        /// <param name="func">The function which will be invoked whenever the delegate is invoked.</param>
        /// <returns>The return value of the invocation.</returns>
        public static Type AddHandler(this object target, string fieldName,
            Func<object[], object> func)
        {
            return InternalAddHandler(target.GetType(), fieldName, func, target, false);
        }

        /// <summary>
        /// Assigns a dynamic handler for a static delegate or event.
        /// </summary>
        /// <param name="targetType">The type where the delegate or event belongs to.</param>
        /// <param name="fieldName">The field name of the delegate or event.</param>
        /// <param name="func">The function which will be invoked whenever the delegate or event is fired.</param>
        /// <returns>The return value of the invocation.</returns>
        public static Type AssignHandler(this Type targetType, string fieldName,
            Func<object[], object> func)
        {
            return InternalAddHandler(targetType, fieldName, func, null, true);
        }

        /// <summary>
        /// Assigns a dynamic handler for a static delegate or event.
        /// </summary>
        /// <param name="target">The object where the delegate or event belongs to.</param>
        /// <param name="fieldName">The field name of the delegate or event.</param>
        /// <param name="func">The function which will be invoked whenever the delegate or event is fired.</param>
        /// <returns>The return value of the invocation.</returns>
        public static Type AssignHandler(this object target, string fieldName, Func<object[], object> func)
        {
            return InternalAddHandler(target.GetType(), fieldName, func, target, true);
        }

        private static Type InternalAddHandler(Type targetType, string fieldName,
            Func<object[], object> func, object target, bool assignHandler)
        {
            Type delegateType;
            var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic |
                               (target == null ? BindingFlags.Static : BindingFlags.Instance);
            var eventInfo = targetType.GetEvent(fieldName, bindingFlags);
            if (eventInfo != null && assignHandler)
                throw new ArgumentException("Event can be assigned.  Use AddHandler() overloads instead.");

            if (eventInfo != null)
            {
                delegateType = eventInfo.EventHandlerType;
                var dynamicHandler = BuildDynamicHandler(delegateType, func);
                eventInfo.GetAddMethod(true).Invoke(target, new Object[] { dynamicHandler });
            }
            else
            {
                var fieldInfo = targetType.GetField(fieldName);
                                                    //,target == null ? BindingFlags.Static : BindingFlags.Instance);
                delegateType = fieldInfo.FieldType;
                var dynamicHandler = BuildDynamicHandler(delegateType, func);
                var field = assignHandler ? null : target == null
                                ? (Delegate)fieldInfo.GetValue(null)
                                : (Delegate)fieldInfo.GetValue(target);
                field = field == null
                            ? dynamicHandler
                            : Delegate.Combine(field, dynamicHandler);
                if (target != null)
                    target.GetType().GetField(fieldName).SetValue(target, field);
                else
                    targetType.GetField(fieldName).SetValue(null, field);
                    //(target ?? targetType).SetFieldValue(fieldName, field);
            }
            return delegateType;
        }

        /// <summary>
        /// Dynamically generates code for a method whose can be used to handle a delegate of type 
        /// <paramref name="delegateType"/>.  The generated method will forward the call to the
        /// supplied <paramref name="func"/>.
        /// </summary>
        /// <param name="delegateType">The delegate type whose dynamic handler is to be built.</param>
        /// <param name="func">The function which will be forwarded the call whenever the generated
        /// handler is invoked.</param>
        /// <returns></returns>
        public static Delegate BuildDynamicHandler(this Type delegateType, Func<object[], object> func)
        {
            var invokeMethod = delegateType.GetMethod("Invoke");
            var parameters = invokeMethod.GetParameters().Select(parm =>
                Expression.Parameter(parm.ParameterType, parm.Name)).ToArray();
            var instance = func.Target == null ? null : Expression.Constant(func.Target);
            var convertedParameters = parameters.Select(parm => Expression.Convert(parm, typeof(object))).Cast<Expression>().ToArray();
            var call = Expression.Call(instance, func.Method, Expression.NewArrayInit(typeof(object), convertedParameters));
            var body = invokeMethod.ReturnType == typeof(void)
                ? (Expression)call
                : Expression.Convert(call, invokeMethod.ReturnType);
            var expr = Expression.Lambda(delegateType, body, parameters);
            return expr.Compile();
        }
    }
}

And i also added some code to test the methods, i could have just used simple lambdas when i assigned the callback delegates, but i rather use the "return true" definitions because i set breakpoints to check that the functions are actually called.

class TestClass 
{
    private void Test()
    {
        TestInstance();
        TestStatic();
    }

    private void TestInstance()
    {
        var eventClass = new EventClass();
        eventClass.InvokeDelegate("InstanceEventRaiseDelegate");
        eventClass.AddHandler("InstanceEvent", parameters =>
            {
                return true;
            });
        eventClass.AddHandler("InstanceEventRaiseDelegate", parameters =>
        {
            return true;
        });
        eventClass.InvokeDelegate("InstanceEventRaiseDelegate");

        eventClass.AssignHandler("InstanceEventRaiseDelegate", parameters =>
        {
            return true;
        });
        eventClass.InvokeDelegate("InstanceEventRaiseDelegate");
    }

    private void TestStatic()
    {
        typeof(EventClass).InvokeDelegate("StaticEventRaiseDelegate");
        typeof(EventClass).AddHandler("StaticEvent", parameters =>
        {
            return true;
        });
        typeof(EventClass).AddHandler("StaticEventRaiseDelegate", parameters =>
        {
            return true;
        });
        typeof(EventClass).InvokeDelegate("StaticEventRaiseDelegate");
        typeof(EventClass).AssignHandler("StaticEventRaiseDelegate", parameters =>
        {
            return true;
        });
        typeof(EventClass).InvokeDelegate("StaticEventRaiseDelegate");
    }

    public class EventClass
    {

        #region Instance

        public EventClass()
        {
            InstanceEventRaiseDelegate = OnInstanceEvent;
        }

        public Action InstanceEventRaiseDelegate;

        public event EventHandler InstanceEvent;

        public void OnInstanceEvent()
        {
            if (InstanceEvent != null)
                InstanceEvent(this, EventArgs.Empty);
        }

        #endregion

        #region Static

        static EventClass()
        {
            StaticEventRaiseDelegate = OnStaticEvent;
        }

        public static Action StaticEventRaiseDelegate;

        public static event EventHandler StaticEvent;

        public static void OnStaticEvent()
        {
            if (StaticEvent != null)
                StaticEvent(null, EventArgs.Empty);
        }

        #endregion
    }
}

Sorry for the late response, but it seems you were able to find the solution elsewhere :), goodluck.

matei.navidad
  • 676
  • 6
  • 18