C#传递参数 - 动态事件订阅

时间:2013-03-24 19:09:58

标签: c# .net events delegates observer-pattern

我在控制器中有以下内容,并希望将float类型的x,y和z传递给订阅者。然而,我遇到了一些困难。我需要调整什么才能让我将浮点数(x,y,z)作为参数传递?谢谢。

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

其他来源:

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

活动发布者:

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

处理程序:

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

2 个答案:

答案 0 :(得分:1)

我添加了一些可以满足您需求的代码:

我将getArgExpression功能修改为此

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

调用函数保持不变,因为它有IEnumerable<Expression>的重载。 然后我添加了特定于MousePosition情况的EventArgs

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

此外,我在'EventProxy'中编写了一个新函数,它将处理一个具有相同类型的3个参数的委托

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

现在,我们可以使用以下代码轻松添加MouseEvent订阅

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

设计方面,这个解决方案并不是最好的,它使用了您所展示的当前设计,但我对使用这种方法维护可扩展且易于遵循的架构有很强的保留意见。 我建议你创建一个从EventArgs类生成lambda参数的适配器/转换器,它们可以通过实现一个接口在EventArgs中轻松指定,这样getArgExpression只会使用转换器生成适当的参数,而你可以摆脱许多if (Type == typeof(...))

我需要更多时间来制定解决方案,但如果您真的感兴趣,我可以尝试将其写下来并发布。

答案 1 :(得分:1)

好的,所以我读了你所说的关于以类似MVC的方式解耦应用程序模块的内容。我通常喜欢使用强类型代码,即使使用反射,但我对MVC相对较新,并且不了解推荐的做法。你比我更了解你的要求,所以我只是编辑了Nguyen的解决方案,因为我相信他正在使用一些未包含在文件中的扩展,并在此处发布了结果。所有积分均归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();
        }
    }
}

我还添加了一些代码来测试方法,我可以在分配回调委托时使用简单的lambdas,但我宁愿使用“return true”定义,因为我设置断点来检查函数是否实际被调用

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

对于迟到的回复感到抱歉,但似乎你能在其他地方找到解决方案:),goodluck。