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

本文关键字:事件 动态 参数 | 更新日期: 2023-09-27 18:05:30

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

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

好的,我读了你说的mvc风格的解耦应用模块。我通常喜欢使用强类型代码,即使在使用反射时也是如此,但我对MVC比较陌生,不知道推荐的实践。你比我更了解你的需求,所以我简单地编辑了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
    }
}

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

我已经添加了一些代码,将实现您所需要的:

我将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>有过载。然后我添加了EventArgs特定于你的MousePosition情况

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(...))

我要花更多的时间来制定解决方案,但如果你真的感兴趣,我可以试着写下来,并张贴。