Question

I am trying to port this example of dynamic double dispatching to C#. I've got the example to work, but I feel like I've shortcut the DynamicDispatch method in the MessageBase class a bit by using Reflection to create the required handler and invoke the appropriate method. Could any provide some advice on how to change it? The example uses the C++ dynamic_cast operator and I'm not sure what the equivalent would be for C#. I'm not sure my solution is the right/best way.

Note: I'm using 3.5 so I can not use the dynamic keyword

Here is the code:

IMessage

public interface IMessage
{
   void Dispatch(IHandler handler);
}

MessageBase

public abstract class MessageBase : IMessage
{
   public abstract void Dispatch(IHandler handler);

   // This is my concern, doesnt feel like the right way to do this
   protected void DynamicDispatch<MessageType>(IHandler handler, MessageType self)
   {
      // Get the messages derived type
      Type self_type = self.GetType();   
      // Create actual message specific handler
      Type message_handler = typeof(IMessageHandler<>).MakeGenericType(self_type);
      // Get the ProcessMessage method
      MethodInfo minfo = message_handler.GetMethod("ProcessMessage");
      try
      {
         // Invoke it with the message
         minfo.Invoke(handler, new object[] { self });
      }
      catch (TargetException ex)
      {
         // Ignore if method doesnt exist
      }
   }
}

Message

public class Message : MessageBase
{
   public override void Dispatch(IHandler handler)
   {
      DynamicDispatch(handler, this);
   }
}

IHandler

public interface IHandler
{
}

IMessageHandler

public interface IMessageHandler<MessageType> : IHandler
{
   void ProcessMessage(MessageType message);
}

DerivedMessageOne

public class DerivedMessageOne : Message
{
   public int MessageOneField;
}

DerivedMessageTwo

public class DerivedMessageTwo : Message
{
   public int MessageTwoField;
}

DerivedMessageHandlerOne

public class DerivedMessageHandlerOne : IMessageHandler<DerivedMessageOne>,
   IMessageHandler<DerivedMessageTwo>
{
   #region IMessageHandler<MessaegType> Members

   // ************ handle both messages *************** //
   public void ProcessMessage(DerivedMessageOne message)
   {
      // Received Message one, do soemthing with i
      int do_something_with_it = message.MessageOneField;
   } 

   public void ProcessMessage(DerivedMessageTwo message)
   {
      // Received Message two, do soemthing with i
   } 

   #endregion
}

DerivedMessageHandlerTwo

public class DerivedMessageHandlerTwo : IMessageHandler<DerivedMessageOne>
{
   #region IMessageHandler<MessaegType> Members

   // ************ handle just MessageOne *************** //
   public void ProcessMessage(DerivedMessageOne message)
   {
      // Received Message one, do soemthing with i
   }

   #endregion
}

Test Case

IMessage messageOne = new DerivedMessageOne();
IMessage messageTwo = new DerivedMessageTwo();
IHandler handlerOne = new DerivedMessageHandlerOne();
IHandler handlerTwo = new DerivedMessageHandlerTwo();

messageOne.Dispatch(handlerOne);
messageOne.Dispatch(handlerTwo);

messageTwo.Dispatch(handlerOne);
messageTwo.Dispatch(handlerTwo);
Was it helpful?

Solution

In C# you want the as operator. http://msdn.microsoft.com/en-us/library/vstudio/cscsdfbt.aspx

It behaves exactly like C++ dynamic_cast.

Here it would be used something like:

IMessageHandler<MessageType> handlerTarget = handler as IMessageHandler<MessageType>;
handlerTarget.ProcessMessage(message);

As you noted, you would have to pass the message type into the message base, something like:

class MessageBase
{
    protected void DoDispatch<T>(T m)
    {
        // ...
    }
}
class Message<T> : MessageBase where T : class
{
    public void Dispatch()
    {
        DoDispatch<T>(this as T);
    }
}
class MyMessage : Message<MyMessage>
{
}

OTHER TIPS

I found this DoubleDispatch that works fine without dynamic:

namespace DoubleDispatch
{
public interface IMessage
{
    void Dispatch(HandlerBase handler);
}

public interface IHandler
{
    void HandleDefault(IMessage message);
}

public interface IHandler<in TMessage> : IHandler
    where TMessage : class, IMessage
{
    void HandleSpecific(TMessage message);
}

public interface IMessage<TMessage, THandler> : IMessage
    where TMessage : class, IMessage<TMessage, THandler>
    where THandler : class, IHandler<TMessage>
{
}

public abstract class HandlerBase : IHandler
{
    public void HandleDefault(IMessage message)
    {
        Console.WriteLine("HandlerBase process {0}", message.GetType().Name);
    }
}

public abstract class MessageBase<TMessage, THandler>
    : IMessage<TMessage, IHandler<TMessage>>
    where TMessage : class, IMessage, IMessage<TMessage, IHandler<TMessage>>
    where THandler : class, IHandler, IHandler<TMessage>
{
    public void Dispatch(HandlerBase handler)
    {
        var runtimeHandler = handler as THandler;
        if (runtimeHandler != null)
        {
            var runtimeMessage = this as TMessage;
            if (runtimeMessage != null)
            {
                runtimeHandler.HandleSpecific(runtimeMessage);
                return;
            }
        }
        handler.HandleDefault(this);
    }
}

public class FirstMessage : MessageBase<FirstMessage, IHandler<FirstMessage>>
{
}

public class SecondMessage : MessageBase<SecondMessage, IHandler<SecondMessage>>
{
}

public class FirstHandler : HandlerBase, IHandler<FirstMessage>
{
    public void HandleSpecific(FirstMessage message)
    {
        Console.WriteLine("FirstHandler process {0}", message.GetType().Name);
    }
}

public class SecondHandler : HandlerBase, IHandler<SecondMessage>
{
    public void HandleSpecific(SecondMessage message)
    {
        Console.WriteLine("SecondHandler process {0}", message.GetType().Name);
    }
}

public class ThirdHandler : HandlerBase, IHandler<FirstMessage>, IHandler<SecondMessage>
{
    public void HandleSpecific(FirstMessage message)
    {
        Console.WriteLine("ThirdHandler process {0}", message.GetType().Name);
    }

    public void HandleSpecific(SecondMessage message)
    {
        Console.WriteLine("ThirdHandler process {0}", message.GetType().Name);
    }
}

public class EmptyHandler : HandlerBase
{
}

public static class DoubleDispatch
{
    public static void Test()
    {
        var handlers = new HandlerBase[]
        {
            new FirstHandler(),
            new SecondHandler(),
            new ThirdHandler(), 
            new EmptyHandler(), 
        };

        var messages = new IMessage[]
        {
            new FirstMessage(),
            new SecondMessage(), 
        };

        Array.ForEach(messages, m =>
        {
            Array.ForEach(handlers, m.Dispatch);
            Console.WriteLine();
        });
    }
}
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top