【问题标题】:C# Passing Parameters - Dynamic Event SubscriptionC# 传递参数 - 动态事件订阅
【发布时间】:2013-03-24 19:09:58
【问题描述】:

我的控制器中有以下内容,并希望将浮点类型的 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));    }
}

事件代理:

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

【问题讨论】:

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


    【解决方案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();
            }
        }
    }
    

    我还添加了一些代码来测试这些方法,当我分配回调委托时,我可以只使用简单的 lambda,但我宁愿使用“返回 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
        }
    }
    

    很抱歉回复晚了,但您似乎可以在其他地方找到解决方案:),祝你好运。

    【讨论】:

    • 非常感谢您的好先生!我还没有找到解决方案,我会尽快尝试你的代码。
    【解决方案2】:

    我添加了一些代码来满足你的要求:

    我把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&lt;Expression&gt; 的重载。 然后我添加了特定于您的 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(...))

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

    【讨论】:

    • 非常感谢您的有力回复。我目前正在尽最大努力改进我的活动实施,如果能向我展示一个更好的解决方案,我将不胜感激。谢谢。
    • 在线搜索时,我看到您的代码 sn-p 取自 stackoverflow.com/questions/45779/… ,此实现要求您可以使用事件名称注册事件处理程序。如果您能给我更多关于您想要实现的目标的详细信息,我可以提出一个更适合您需求的解决方案。您是否只需要引发和处理 MouseEventArgs,还是需要针对多个通用场景的解决方案?
    • 确实是sn-p。我的目标是创建一个通用解决方案,该解决方案将在多种情况下接受相似类型的参数。例如,MouseEventArgs 和 WorldPositionArgs 都会采用 X、Y、Z 浮点数。希望对您有所帮助,并再次感谢您的帮助。
    • 简要解释一下我正在尝试做的事情:我的目标是保持我的应用程序的每个功能解耦。功能以类似 MVC 的模式构建,其控制器充当入口点。功能控制器负责订阅主要的应用程序事件。例如:我正在向我的 MainApplication 添加一个 Highlight 功能。事件订阅发生在 MainApplicationController 中。 HighlightController 注册到 MainApplicationController 中的“RightClickEvent”。 HighlightController 现在接收从 RightClickEvent 传递的参数。希望对您有所帮助,再次感谢。
    • 告诉你,我没有忘记这件事,我会尽快写一个答案,这个周末 - 最晚。
    猜你喜欢
    • 2010-09-07
    • 1970-01-01
    • 1970-01-01
    • 2022-11-07
    • 2017-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多