Передача параметров C# — динамическая подписка на события

У меня есть ниже в моем контроллере, и я хотел бы передать подписчикам x, y и z типа float. Однако у меня есть некоторые трудности с пониманием. Что мне нужно настроить, чтобы я мог передавать свои поплавки (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);
    }
}

person Nestor Ledon    schedule 24.03.2013    source источник


Ответы (2)


Итак, я прочитал, что вы сказали о разделении модулей приложений в стиле MVC. Обычно мне нравится работать со строго типизированным кодом, даже при использовании отражения, но я относительно новичок в MVC и не знаю рекомендуемых практик. Вы знаете свои требования лучше, чем я, поэтому я просто отредактировал решение Нгуена, потому что я считаю, что он использовал некоторые расширения, которые не были включены в файл, и разместил результат здесь. Все заслуги принадлежат Нгуен.

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

И я также добавил некоторый код для проверки методов, я мог бы просто использовать простые лямбда-выражения, когда я назначал делегаты обратного вызова, но я предпочитаю использовать определения «возвратить истину», потому что я устанавливаю точки останова для проверки того, что функции действительно вызываются.

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

Извините за поздний ответ, но, похоже, вы смогли найти решение в другом месте :), удачи.

person matei.navidad    schedule 29.03.2013
comment
Большое спасибо добрый человек! Я еще не нашел решения, я попробую ваш код, как только у меня будет шанс. - person Nestor Ledon; 29.03.2013

Я добавил некоторый код, который достигнет того, что вам нужно:

Я изменил функцию 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, которая будет обрабатывать делегата с тремя параметрами одного типа.

// 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, их можно легко указать в EventArgs путем реализации интерфейса, таким образом, getArgExpression будет использовать только преобразователь для генерации соответствующих параметров, а вы можно избавиться от многих if (Type == typeof(...)).

Мне понадобится еще некоторое время, чтобы составить это решение, но если вам действительно интересно, я могу попытаться записать его и опубликовать.

person matei.navidad    schedule 25.03.2013
comment
Большое спасибо за ваш развернутый ответ. В настоящее время я изо всех сил стараюсь улучшить реализацию своего события и был бы очень признателен, если бы мог показать мне лучшее решение. Спасибо. - person Nestor Ledon; 26.03.2013
comment
При поиске в Интернете я вижу, что ваш фрагмент кода был взят из stackoverflow.com/questions/45779/ , эта реализация требовала, чтобы вы могли зарегистрировать обработчик событий, используя имя события. Если бы вы могли дать мне более подробную информацию о том, чего именно вы пытаетесь достичь, я мог бы найти решение, которое могло бы лучше соответствовать вашим потребностям. Вам нужно создавать и обрабатывать только MouseEventArgs или вам требуется решение для нескольких общих сценариев? - person matei.navidad; 26.03.2013
comment
Это действительно фрагмент. Моя цель — создать универсальное решение, которое будет принимать параметры схожих типов для нескольких случаев. Например, MouseEventArgs и WorldPositionArgs будут принимать числа с плавающей запятой X, Y, Z. Надеюсь, что это поможет, и еще раз большое спасибо за вашу помощь. - person Nestor Ledon; 26.03.2013
comment
Кратко объясню, что я пытаюсь сделать: Моя цель состоит в том, чтобы каждая функция моего приложения была отделена. Функции построены по шаблону, подобному MVC, с их контроллерами, выступающими в качестве точки входа. Контроллер функций отвечает за подписку на основные события приложения. Пример: я добавляю функцию Highlight в свое приложение MainApplication. Подписки на события происходят в MainApplicationController. HighlightController регистрируется в RightClickEvent в MainApplicationController. HighlightController теперь получает параметры, переданные из RightClickEvent. Надеюсь, это поможет, и еще раз спасибо. - person Nestor Ledon; 26.03.2013
comment
Просто чтобы вы знали, я не забыл об этом, я напишу ответ, как только смогу, в эти выходные - самое позднее. - person matei.navidad; 27.03.2013
comment
Я очень ценю это. Спасибо. - person Nestor Ledon; 27.03.2013
comment
Возможно, это звучит близко к тому, что мне нужно: buunguyen. net/blog/revisit-dynamic-event-handler-in-net.html - person Nestor Ledon; 27.03.2013