5

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

我目前正在尝试减少当前项目中事件句柄的数量。我们有多个通过 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);
    }
}

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

4

5 回答 5

3

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

于 2009-07-29T10:21:04.563 回答
1

我可以让 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 }));
        }
于 2009-07-14T02:26:14.600 回答
1

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

干杯,
贝里尔

于 2009-07-27T00:18:50.213 回答
1

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

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

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

于 2009-07-28T10:07:27.350 回答
0

如果您尝试减少事件句柄的数量以节省 RAM,请执行 microsoft 所做的操作(在 System.ComponentModel.Component 中)并使用EventHandlerList来跟踪您的所有事件。这是一篇描述使用 EventHandlerList 节省内存使用的文章,这是一篇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(但使用不同的键)。

于 2009-07-29T10:13:33.473 回答