views:

395

answers:

5

Design Question – Polymorphic Event Handling

I’m currently trying to reduce the number of Event Handles in my current project. We have multiple systems that send data over USB. I currently have a routine to read in the messages and parse the initial header details to determine which system the message came from. The headers are a little different, so the EventArgs I created are not the same. Then I notify all “observers” of the change. So what I have right now is the following:

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

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

    public event TestHandlerCurrentlyDoing mEventArgs1;
    public event TestHandlerCurrentlyDoingAlso mEventArgs2;

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

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

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

What I really want to do is this:

    public delegate void TestHandlerDesired(MyEvent1Args eventArgs1);
    public delegate void TestHandlerDesired(MyEvent2Args eventArgs2);

    public event TestHandlerDesired mEventArgs;

        mEventArgs += new TestHandlerDesired (Form1_mEventArgs1);
        mEventArgs += new TestHandlerDesired (Form1_mEventArgs2);

And for ambiguity reasons we can’t do this. So my question is what would be a better approach to this problem?

+1  A: 

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 }));
        }
SwDevMan81
+1  A: 

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

Berryl
+1  A: 

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.

Grzenio
A: 

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).

Rob Fonseca-Ensor
+3  A: 

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)

Rob Fonseca-Ensor
Ok, so sort of like this artical where its implemented using the visitor pattern http://www.ddj.com/cpp/184403497?pgno=3, this seems like a great approach
SwDevMan81
yes, the visitor pattern is probably the biggest user of double dispatch. It's a bit easier in C#. This article starts with a traditional (compile time, no reflection) version and ends up with a reflective version: http://accu.org/index.php/journals/1376 and here's another one that makes use of reflection: http:/www.stackoverflow.com/questions/42587/
Rob Fonseca-Ensor