【问题标题】:Design Help – Polymorphic Event Handling设计帮助——多态事件处理
【发布时间】:2013-05-08 02:00:17
【问题描述】:

设计问题——多态事件处理

我目前正在尝试减少当前项目中事件句柄的数量。我们有多个通过 USB 发送数据的系统。我目前有一个例程来读取消息并解析初始标头详细信息以确定消息来自哪个系统。标头有点不同,所以我创建的 EventArgs 不一样。然后我通知所有“观察者”这一变化。所以我现在拥有的是以下内容:

public enum Sub1Enums : byte
{
    ID1 = 0x01,
    ID2 = 0x02
}

public enum Sub2Enums : ushort
{
    ID1 = 0xFFFE,
    ID2 = 0xFFFF
}

public class MyEvent1Args
{
    public Sub1Enums MessageID;
    public byte[] Data;
    public MyEvent1Args(Sub1Enums sub1Enum, byte[] data)
    {
        MessageID = sub1Enum;
        Data = data;
    }
}

public class MyEvent2Args
{
    public Sub2Enums MessageID;
    public byte[] Data;
    public MyEvent2Args(Sub2Enums sub2Enum, byte[] data)
    {
        MessageID = sub2Enum;
        Data = data;
    }
}

Form1 代码

public class Form1
{
    public delegate void TestHandlerCurrentlyDoing(MyEvent1Args eventArgs1);
    public delegate void TestHandlerCurrentlyDoingAlso(MyEvent2Args eventArgs2);

    public event TestHandlerCurrentlyDoing mEventArgs1;
    public event TestHandlerCurrentlyDoingAlso mEventArgs2;

    public Form1()
    {
        mEventArgs1 += new TestHandlerCurrentlyDoing(Form1_mEventArgs1);
        mEventArgs2 += new TestHandlerCurrentlyDoingAlso(Form1_mEventArgs2);
    }

    void Form1_mEventArgs2(MyEvent2Args eventArgs2)
    {
        // Do stuff here
        Sub2Enums mid = my_event2_args.MessageID;
        byte[] data = my_event2_args.Data;
    }

    void Form1_mEventArgs1(MyEvent1Args eventArgs1)
    {
        // Do stuff here
        Sub1Enums mid = my_event1_args.MessageID;
        byte[] data = my_event1_args.Data;
    }

在解析算法中,根据它是哪条消息,我有类似的东西:

void ParseStuff()
{
    if (mEventArgs1 != null)
    {
        mEventArgs1(new MyEvent1Args(Sub1Enums.ID1, new byte[] { 0x01 }));
    }
    if (mEventArgs2 != null)
    {
        mEventArgs2(new MyEvent2Args(Sub2Enums.ID2, new byte[] { 0x02 }));
    }
}

我真正想做的是:

public class Form1
{
    public delegate void TestHandlerDesired(MyEvent1Args eventArgs1);
    public delegate void TestHandlerDesired(MyEvent2Args eventArgs2);

    public event TestHandlerDesired mEventArgs;

    public Form1()
    {
        mEventArgs += new TestHandlerDesired (Form1_mEventArgs1);
        mEventArgs += new TestHandlerDesired (Form1_mEventArgs2);
    }
}

由于模棱两可的原因,我们不能这样做。所以我的问题是什么是解决这个问题的更好方法?

【问题讨论】:

  • 看起来像 C# 语法。如果是这样,请考虑将此详细信息添加到您的问题中。另外,我认为你可以重新标记它以将C#作为标记,然后你会有更多的视图。

标签: c# events event-handling messaging


【解决方案1】:

如果您尝试减少事件句柄的数量以抽象/简化您必须执行的编码,那么将Double Dispatch 设计模式应用于您的事件参数将是完美的。对于必须执行安全类型转换(/ 是 instanceof 检查)而言,这基本上是一个优雅(但冗长)的解决方案

【讨论】:

  • 好的,有点像这篇文章,它使用访问者模式ddj.com/cpp/184403497?pgno=3 实现,这似乎是一个很好的方法
  • 是的,访问者模式可能是双重调度的最大用户。在 C# 中它更容易一些。本文从传统(编译时,无反射)版本开始,以反射版本结束:accu.org/index.php/journals/1376,这是另一篇使用反射的文章:http://www.stackoverflow.com/questions/42587/跨度>
  • 我确实实现了这一点并将其发布在问题Design pattern for handling multiple message types 下作为答案,感谢@Rob Fonseca-Ensor 的建议
【解决方案2】:

我可以让 MyEvent1Args 和 MyEvent2Args 从一个公共基类派生并执行以下操作:

public class BaseEventArgs : EventArgs
{
    public byte[] Data;
}

public class MyEvent1Args : BaseEventArgs
{ … }
public class MyEvent2Args : BaseEventArgs
{ … }


public delegate void TestHandlerWithInheritance(BaseEventArgs baseEventArgs);

public event TestHandlerWithInheritance mTestHandler;

mTestHandler += new TestHandlerWithInheritance(TestHandlerForEvent1Args);
mTestHandler += new TestHandlerWithInheritance(TestHandlerForEvent2Args);

    void TestHandlerForEvent1Args(BaseEventArgs baseEventArgs)
    {
        MyEvent1Args my_event1_args = (baseEventArgs as MyEvent1Args);
        if (my_event1_args != null)
        {
            // Do stuff here
            Sub1Enums mid = my_event1_args.MessageID;
            byte[] data = my_event1_args.Data;
        }
    }

    void TestHandlerForEvent2Args(BaseEventArgs baseEventArgs)
    {
        MyEvent2Args my_event2_args = (baseEventArgs as MyEvent2Args);
        if (my_event2_args != null)
        {
            // Do stuff here
            Sub2Enums mid = my_event2_args.MessageID;
            byte[] data = my_event2_args.Data;
        }
    }

在解析算法中,根据它是哪条消息,我有类似的东西:

        if (mTestHandler!= null)
        {
            mTestHandler (new MyEvent1Args(Sub1Enums.ID1, new byte[] { 0x01 }));
        }
        if (mTestHandler!= null)
        {
            mTestHandler (new MyEvent2Args(Sub2Enums.ID2, new byte[] { 0x02 }));
        }

【讨论】:

    【解决方案3】:

    从多态中休息一下,看看使用间接,特别是事件聚合器模式,如果你还没有的话; Fowler 首先@@http://martinfowler.com/eaaDev/EventAggregator.html,然后如果您需要更多想法,则由 Jeremy Miller 发帖。

    干杯,
    浆果

    【讨论】:

      【解决方案4】:

      你可以考虑几个选项(我不确定你到底想在这里实现什么):

      1. 创建 EventArgs 的层次结构,并让观察者负责过滤他们感兴趣的内容(这是您在回答中提出的内容)。如果某些观察者对多种类型的消息感兴趣(理想情况下由基类类型描述),这尤其有意义。

      2. 不要使用 .Net 委托,只需自己实现它,这样当您注册委托时,它也会采用它所期望的事件类型。这假设您已完成 (1) 中的工作,但您希望将过滤传递给您的班级而不是观察者

      例如(未经测试):

      enum MessageType
      {
      Type1,Type2
      }
      private Dictionary<MessageType, TestHandlerWithInheritance> handlers;
      public void RegisterObserver(MessageType type, TestHandlerWithInheritance handler)
      {
        if(!handlers.ContainsKey(type))
        {
          handlers[key] = handler;
        }
        else
        {
          handlers[key] = Delegate.Combine(handlers[key] , handler);
        }
      }
      

      当新消息到达时,您从处理程序字典中运行正确的委托。

      3. 以在 WinForms 中完成的方式实现事件,这样您就不会有一个永远暴露事件的底层事件。如果您希望拥有比观察者更多的事件,这是有道理的。

      例如:

      public event EventHandler SthEvent
      {
          add
          {
              base.Events.AddHandler(EVENT_STH, value);
          }
          remove
          {
              base.Events.RemoveHandler(EVENT_STH, value);
          }
      }
      
      public void AddHandler(object key, Delegate value)
      {
          ListEntry entry = this.Find(key);
          if (entry != null)
          {
              entry.handler = Delegate.Combine(entry.handler, value);
          }
          else
          {
              this.head = new ListEntry(key, value, this.head);
          }
      }
      
      
      public void RemoveHandler(object key, Delegate value)
      {
          ListEntry entry = this.Find(key);
          if (entry != null)
          {
              entry.handler = Delegate.Remove(entry.handler, value);
          }
      }
      
      
      private ListEntry Find(object key)
      {
          ListEntry head = this.head;
          while (head != null)
          {
              if (head.key == key)
              {
                  return head;
              }
              head = head.next;
          }
          return head;
      }
      
      private sealed class ListEntry
      {
          // Fields
          internal Delegate handler;
          internal object key;
          internal EventHandlerList.ListEntry next;
      
          // Methods
          public ListEntry(object key, Delegate handler, EventHandlerList.ListEntry next)
          {
              this.next = next;
              this.key = key;
              this.handler = handler;
          }
      }
      

      如果您希望我扩展任何答案,请告诉我。

      【讨论】:

        【解决方案5】:

        如果您尝试减少事件句柄的数量以节省 RAM,请执行 microsoft 所做的操作(在 System.ComponentModel.Component 中)并使用EventHandlerList 来跟踪您的所有事件。 Here is an article that describes conserving memory use with an EventHandlerListhere is a similar article that's written in C#.

        它的要点是你可以在你的类中声明一个 EventHandlerList(记得释放它),以及一个唯一的键:

        public class Foo
        {
            protected EventHandlerList listEventDelegates = new EventHandlerList();
            static readonly object mouseDownEventKey = new object();
        

        ...覆盖事件属性:

        public event MouseEventHandler MouseDown {  
           add { listEventDelegates.AddHandler(mouseDownEventKey, value); }
           remove { listEventDelegates.RemoveHandler(mouseDownEventKey, value); }
        }
        

        ...并提供一个 RaiseEvent 方法:

        protected void RaiseMouseDownEvent(MouseEventArgs e)
        {
            MouseEventHandler handler = (MouseEventHandler) base.Events[mouseDownEventKey];
            if (handler != null)
            {
                handler(this, e);
            }
        }
        

        当然,您只需为所有事件重用相同的 EventHandlerList(但使用不同的键)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-02
          • 2012-11-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多