Question

I have a message class that holds the name of a destination, and a generic variable acting as the message payload

public class Message<T> {
    public string Destination
    public T Payload

    // ... constructors, functions, etc. ...
}

This way, I should be able to create messages of any type (without needing to explicitly list the types that are valid) and still have the compiler test the type-safety.

I want to create a method that generates a message. The problem is I don't want to return a specific kind of message, I just want to return any message, or perhaps an array/List of messages with different types. I.e. instead of explicitly returning Message<String> or Message<int> I just want it to return Message. That way I can pass the message to the destination, then the destination can figure out what the type is and consume the payload.

The problem is, if I do that, it'll involve a lot of typecasting and kinda defeats the point of using generics. I might as well just make Payload an object then typecast it in the destination. Or just use dynamic. Should I or shouldn't I use object or dynamic in this case, and why?

This is actually a rephrasing of a previous poorly-phrased question that I asked. Previously, instead of using a generic type, I would use dynamic for the message, but answers kept saying that using dynamic was unsuitable except for things like Python interop, and many were referring to the use of generics which at the time I didn't think was suitable because of the above reason. My previous question is here: When to not use dynamic in C#

EDIT: As suggested by Benjamin, here's what I'd like to happen

I have a "manager" that has a bunch of model objects in a dictionary. In it there's a function that goes like this:

void ManagerFunction()
{
    Message m = getMessage();
    objects[m.Destination].handleMessage(m)
}

Notice that the manager doesn't care about the payload.

This have a function generating a message and returning it.

Message getMessage()
{
    Message m;

    if(x)
        m = new Message<String>();
    else if(y)
        m = new Message<int>();
    // ... some more else-if's here ...
    else
        m  = new Message<double>();

    return m;
}

Notice how the message generating function can send a message of any type

Let's say that the destination all consist of objects that implement the following interface

interface DestinationInterface{
    void HandleMessage(Message m)
}

I would like it so that one object might handle the interface in the following way

void HandleMessage(Message m)
{
    if(m.Payload is String){
        String s = (String) m.Payload
        // ...
    } else throw Exception();
}

And another object that implements the interface might handle the interface in a different way

void HandleMessage(Message m)
{
    if(m.Payload is int)
    {
        int i = (int) m.Payload
        // ...
    } else throw Exception();
}

Because of the typecasting, a generic type wouldn't be useful. So should I just use object for the payload? Why not use dynamic and get rid of all the typecasting in the first place?

Was it helpful?

Solution

You could skip using generic messages and instead use generic handlers.

Here is how we do it:

public interface IMessage
{
}

public interface IHandle<in T> where T : IMessage
{
    void Handle(T message);
}

Implement IMessage with "payload properties" that actually describes the payload.

public class ConcreteMessage : IMessage
{
    ...
    public int BetterNameThanPayload { get; private set; }
}

Implement a message handler:

public class ConcreteHandler : IHandle<ConcreteMessage>
{
    public void Handle(ConcreteMessage message) 
    {
        var foo = message.BetterNameThanPayload;
        ...
    }
}

Use dynamic keyword when calling the handler (this should be the only place to use 'dynamic'):

void ManagerFunction()
{
    IMessage m = getMessage();
    var messageHandlerType = typeof(IHandle<>).MakeGenericType(m.GetType());
    var messageHandler = [resolve message handler];
    ((dynamic)messageHandler).Handle((dynamic)message);
}

You could use a "destination" string to resolve the handler, but if using an IOC container it's simpler to resolve it by type (and it's type-safe). Also, skipping "destination" will decouple the message from the handler.

Licensed under: CC-BY-SA with attribution
scroll top