Design Help – Polymorphic Event Handling

那年仲夏 提交于 2019-12-04 09:40:29

If you're trying to reduce the number of event handles in order abstract / simplify the coding you have to do, then applying the Double Dispatch design pattern to your event args would be perfect. It's basically an elegant (but wordy) fix for having to perform safe type casts (/ is instanceof checks)

I could make MyEvent1Args and MyEvent2Args derive from a common base class and do the following:

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

And in the parse algorithm I have something like this based on which message it is:

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

Take a break from polymorphism and look into using indirection, specifically the Event Aggregator pattern if you haven't already; Fowler first @ http://martinfowler.com/eaaDev/EventAggregator.html and then postings by Jeremy Miller if you need more ideas.

Cheers,
Berryl

You could consider a few options (I am not sure what exactly you want to achieve here):

1. Create a hierarchy of EventArgs and make the observers responsible for filtering stuff they are interested in (this is what you proposed in your answer). This especially makes sense if some observers are interested in multiple types of messages, ideally described by the base class type.

2. Don't use .Net delegates, just implement it yourself such that when you register the delegate it also takes the type of event it expects. This assumes you have done the work from (1), but you want to pass the filtering to your class and not observers

E.g. (untested):

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

And when a new message arrives, you run the correct delegate from the handlers dictionary.

3. Implement events in the way its done in WinForms, so that you don't have an underlying event for ever exposed event. This makes sense if you expect to have more events than observers.

E.g.:

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

Please let me know if you want me to expand on any of the answers.

If you're trying to reduce the number of event handles to save RAM, do what microsoft do (in System.ComponentModel.Component) and use an EventHandlerList to track all of your events. Here is an article that describes conserving memory use with an EventHandlerList, and here is a similar article that's written in C#..

The gist of it is that you can declare a single EventHandlerList (remember to dispose it) in your class, along with a unique key:

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

...override the event property:

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

...and provide a RaiseEvent method:

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

Of course, you just reuse the same EventHandlerList for all your events (but with different keys).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!