【问题标题】:Add event handler using reflection使用反射添加事件处理程序
【发布时间】:2020-03-02 18:40:34
【问题描述】:

我有一个带有所谓命令的.json 文件:

"Commands":[{
   "EventName": "MouseLeftButtonUp",
   "MethodToExecute": "NextJson",
   "Args": "Next.json"
},{
   "EventName": "MouseRightButtonUp",
   "MethodToExecute": "CloseApp"
}

我将这个 json 反序列化为这个类:

    public class Command
    {
        [JsonPropertyName("EventName")]
        public string EventName { get; set; }

        [JsonPropertyName("MethodToExecute")]
        public string MethodToExecute { get; set; }

        [JsonPropertyName("Args")]
        public string Args { get; set; }

        /*Methods*/
    }

EventNameUIElement 类事件的名称。 MethodToExecute 是事件触发时调用的方法名称。 Args 是参数,传递给MethodToExecute

我不希望我的用户能够调用应用程序中的任何方法,所以我不使用反射来获取MethodInfo,而是创建DictionaryDictionary<string, Delegate> MethodsDictionary。这个字典中的key是方法的名称(MethodToExecute来自Command类),值是这样的:

MethodsDictionary.Add(nameof(CloseApp), new Action(CloseApp));
MethodsDictionary.Add(nameof(NextJson), new Action<string>(NextJson));

没有使用反射,我添加了这样的事件处理程序:

button.MouseLeftButtonUp += (sender, args) => MethodsDictionary[command.MethodToExecute].DynamicInvoke(command.Args);

但我想对事件进行动态绑定。好吧,我当然可以在command.Name 属性上通过丑陋的switch-case,但我仍然想尝试使用反射的解决方案。 在我看来,解决方案应该类似于:

foreach (var command in commands)
{
    command.Bind(uielement, MethodsDictionary[command.MethodToExecute]);
}

//And command.Bind method is like:

public void Bind(UIElement uielement, Delegate methodToExecute)
{
    //I know there's no such method like GetEventHandler, just an example
    var handler = uielement.GetEventHandler(EventName);
    handler += (sender, args) => methodToExecute.DynamicInvoke(Args);
}

我搜索了几个非常相似的问题:

Subscribe to an event with Reflection

https://social.msdn.microsoft.com/Forums/vstudio/en-US/d7f184f1-0964-412a-8659-6759a0e2db83/c-reflection-problem-subscribing-to-event?forum=netfxbcl

https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/how-to-hook-up-a-delegate-using-reflection

AddEventHandler using reflection

Add Event Handler using Reflection ? / Get object of type?

但是这些并不能帮助我以我想要的方式解决问题。我尝试了上面的一些解决方案,但它们对我不起作用,失败并出现不同的异常。

更新。

如上所述,我尝试通过switch-case 实现处理程序绑定。它导致了Command类中的这个方法:

public void Bind(UIElement element)
{
    switch (this.Name)
    {
        case "MouseRightButtonUp":
        {
            element.MouseRightButtonUp += (sender, args) => MethodsDictionary[this.MethodToExecute].DynamicInvoke(this.Args);
            break;
        }
        case "Click":
        {
            //UIElement doesn't have Click event
            var button = element as ButtonBase;
            button.Click += (sender, args) => MethodsDictionary[this.MethodToExecute].DynamicInvoke(this.Args);
            break;
        }
        /*And so on for each event*/
        default:
        {
            throw new NotSupportedException();
        }
    }
}

我不喜欢,添加新处理程序的那部分只是上一节的复制粘贴,但在这种情况下我看不到另一种解决方法。我想在这种情况下使用反射,但我不知道是否可以。

【问题讨论】:

    标签: c# .net wpf events reflection


    【解决方案1】:

    如果您无法让反射起作用,您可以使用另一个 Dictionary 来存储支持的事件订阅者方法:

    Command.cs

    public class Command
    {
        [JsonPropertyName("EventName")]
        public string EventName { get; set; }
    
        [JsonPropertyName("MethodToExecute")]
        public string MethodToExecute { get; set; }
    
        [JsonPropertyName("Args")]
        public string Args { get; set; }
    
        /*Methods*/
    }
    

    JsonEventMapper.cs

    class JsonEventMapper
    {
      private Dictionary<string, Action<UIElement, EventHandler>>> SupportedEventSubscriberMap { get; }
      private Dictionary<string, EventHandler> RegisteredEventHandlerMap { get; }   
    
      public JsonEventMapper()
      {
        this.SupportedEventSubscriberMap = new Dictionary<string, Action<UIElement, EventHandler>>() 
        { 
          { 
            nameof(UIElement.MouseRightButtonUp), (uiElement, handler) => uiElement.MouseRightButtonUp += handler.Invoke 
          },
          { 
            nameof(UIElement.LostFocus), (uiElement, eventSubscriber) => uiElement.LostFocus += handler.Invoke 
          }
        };
    
        this.RegisteredEventHandlerMap = new Dictionary<string, EventHandler>()
        {
          {
            nameof(UIElement.MouseLeftButtonUp), CloseApp
          },
          {
            nameof(UIElement.LostFocus), NextJson
          }
        };
      }
    
      public void RegisterJsonCommands(IEnumerable<Command> commands, UIElement uiElement)
      {    
        foreach (var command in commands)
        {
          BindCommandToEvent(uiElement, command);
        }
      }
    
      private void BindCommandToEvent(UIElement uiElement, Command command)
      {    
        if (this.SupportedEventSubscriberMap.TryGetValue(command.EventName, out Action<UIElement, EventHandler> eventSubscriber)
          && this.RegisteredEventHandlerMap.TryGetValue(command.EventName, out EventHandler eventHandler))
        {
          eventSubscriber.Invoke(uiElement, eventHandler);
        }
      }
    
      private void CloseApp(object sender, EventArgs args)
      {    
        // Handle event
      }
    
      private void NextJson(object sender, EventArgs args)
      {    
        // Handle event
      }
    }
    

    用法

    IEnumerable<Command> commands = DeserializeJsonToCommands();
    var eventMapper = new JsonEventMapper();
    eventMapper.RegisterJsonCommands(commands, button);
    

    您肯定想根据具体情况调整细节。

    不要忘记取消订阅事件(例如,通过定义另一个 SupportedEventUnsubscriberMap)。

    【讨论】:

    • 感谢您的详细解答!正如我在 OP 中提到的,我尝试通过switch-case 实现处理程序绑定。它在Command 类中产生了这个方法Bind(更新的OP)。我不喜欢,添加新处理程序的那部分只是上一节的复制粘贴,但在这种情况下我看不到另一种解决方法。我还没有尝试过你的解决方案,但从代码来看,我认为它有几乎相同的问题。我想在这种情况下使用反射,但我不知道是否可以。
    • 是的,反射当然有用。您已经发布了一些简单的示例,但您似乎无法使其工作。您也永远不会详细了解您的尝试到底是什么不起作用,所以我认为您可能会对无反射解决方案更满意。如果你仍然想让反射工作,你应该发布你的代码并描述究竟是什么不工作以及你得到的确切错误。然后我会看看它。
    • Here's 我的带有示例应用程序的仓库。我还包括了通过反射和用 cmets 编写的最接近的绑定变体(根据先前问题的答案来判断)什么是不工作的(参见 Models/CommandModel.cs)。
    【解决方案2】:

    我认为您正在尝试做的事情是这样的:

    public class CommandModel
    {
        /// properties
    
        public void Bind(UIElement element)
        {
            EventBinder.Bind(() => MainViewModel.RunCommand(MethodToExecute, Args), element, EventName);
        }
    }
    
    public static class EventBinder
    {
        public static void Bind(Action action, object eventSource, string eventName)
        {
            var eventInfo = eventSource.GetType().GetEvent(eventName);
    
            EventHandler eventHandler = (s, e) => action();
    
            eventInfo.AddEventHandler(eventSource, ConvertDelegate(eventHandler, eventInfo.EventHandlerType));
        }
    
        private static Delegate ConvertDelegate(Delegate originalDelegate, Type targetDelegateType)
        {
            return Delegate.CreateDelegate(
                targetDelegateType,
                originalDelegate.Target,
                originalDelegate.Method);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-24
      • 1970-01-01
      • 2022-01-14
      • 2014-02-07
      • 1970-01-01
      • 2012-01-18
      相关资源
      最近更新 更多