Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design Help – Polymorphic Event Handling

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

Form1 code

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

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

void ParseStuff()
{
    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 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);
    }
}

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

like image 674
SwDevMan81 Avatar asked Jul 01 '09 22:07

SwDevMan81


4 Answers

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)

like image 126
Rob Fonseca-Ensor Avatar answered Nov 18 '22 11:11

Rob Fonseca-Ensor


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 }));
        }
like image 34
SwDevMan81 Avatar answered Nov 18 '22 09:11

SwDevMan81


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

like image 1
Berryl Avatar answered Nov 18 '22 10:11

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.

like image 1
Grzenio Avatar answered Nov 18 '22 11:11

Grzenio