Ignoring the generic part of a type while an object is being passed
https://softwareengineering.stackexchange.com/questions/256598
-
05-10-2020 - |
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?
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.