0

The title might be little misleading, but I am not sure how to word it appropriately, so if anyone knows a better title, please edit.

I have a method that takes a generic type that has to be derived from MessageBase (ReadMessage). Now I have a list of class names that all inherit from MessageBase, like so:

public Dictionary<int,string> messageBaseNames = new Dictionary<int,string>();

Lets say my dictionary looks like this:

messageBaseNames = {
    { 1 : "MyMessageBase01" },
    { 2 : "MyMessageBase02" },
    { 3 : "AnotherMessageBase" }
}

The ReadMessage method is usually used as follows:

public void ProcessMessageBase(NetworkMessage netMsg) {
    var msg = netMsg.ReadMessage<MyMessageBase01>();
}

Is it possible to pass those string class representations to the ReadMessage generic type? The netMsg has an short value so that I know which string is the correct one.

Just as an addition, this is the signature of ReadMessage: public TMsg ReadMessage<TMsg> () where TMsg : MessageBase, new();

And for clarification:

I'm sending a bunch of different network messages that I want to a aggregate in a single function and distribute from that method. For that to work I need to make that ReadMessage function dynamic to accommodate different MessageBase types.

ShitakeOishii
  • 89
  • 2
  • 9
  • 1
    Would it be okay for you when then the `msg`variable in your example has type `MessageBase` instead of the concrete type? – NineBerry Jan 30 '17 at 20:09
  • 1
    Possible duplicate of [How do I use reflection to call a generic method?](http://stackoverflow.com/questions/232535/how-do-i-use-reflection-to-call-a-generic-method) – NineBerry Jan 30 '17 at 20:12
  • @NineBerry No, unfortunately it has to be the concrete type. – ShitakeOishii Jan 30 '17 at 20:57
  • 1
    This won't work. The compiler has to know the type of the variable at compile time. You have to rethink your architecture. Show us some more of the background of the question. – NineBerry Jan 30 '17 at 21:03
  • @NineBerry Just added a bit more information, maybe it helps to understand the problem. – ShitakeOishii Jan 30 '17 at 21:28
  • 1
    Is having a dictionary with the type names as values the only option? Did you choose this set up or is it imposed? – InBetween Jan 30 '17 at 21:46
  • 1
    @InBetween It is chosen. If you have any other option, I would gladly take any advice. – ShitakeOishii Jan 30 '17 at 21:56
  • 1
    I was about to post the same answer [Phil1970 has](http://stackoverflow.com/a/41946084/767890); simply create a dictionary of delegates with the right type. – InBetween Jan 30 '17 at 21:57
  • When you say it has to be the concrete type (and not `MessageBase`), please explain why as you essentially won't be able to call any specific code given the way generics works in C#... Thus the only purpose would be for operator like `typeof` or reflection... – Phil1970 Jan 30 '17 at 21:59
  • @Phil1970 I'm not 100% sure, but I think that's just how `Unity` manages it network messages. If I pass in `MessageBase` the serialized message will get an `IndexOutOfBoundsException` because it's not the concrete type and thus can't deserialize the network message. – ShitakeOishii Jan 30 '17 at 22:02

4 Answers4

1

If the only purpose of the dictionary is to call the appropriate ReadMessage method depending on specified integer value, then I would use a dictionary of functions instead.

public dictionary<int, Func<NetworkMessage, MessageBase>> messageBaseReaders;

Then I would initialize them with

messageBaseReaders.Add(1, (nm) => nm.ReadMessage<MyMessageBase01>());
messageBaseReaders.Add(2, (nm) => nm.ReadMessage<MyMessageBase02>());
messageBaseReaders.Add(3, (nm) => nm.ReadMessage<AnotherMessageBase>());

Then I would use them like that:

Func<NetworkMessage, MessageBase> reader;
if (messageBaseReaders.TryGetValue(msgId, out reader))
{
    var msg = reader(netMsg);
}
else
{
    // Desired error handling...
}

Using Action and Func allows much more flexibility with generics in C# (still we relatively simple and efficient code). In that case, you won't need reflection.

Otherwise, if you want reflection, you can refer to Tim's answer.

Alternatively, you might use a dependency injection framework.

Best solution depends on your actual code and what you might need in the future. It might be best to have a public virtual ReadMessage method in your MessageBase class and use the dictionary for creation purpose only. That way, you would respect the SRP principle and make the code more maintainable and extensible.

Phil1970
  • 2,605
  • 2
  • 14
  • 15
  • Good idea, but don't I run into the same trouble here? I don't know my class names before compile time. The dictionary is dynamically created with the class names through reflection. Unless there is a way on how to create the functions in the dictionary dynamically. – ShitakeOishii Jan 30 '17 at 22:01
  • Well my solution assume that all `MessageBase` derived types are visible when the dictionary is created. In some cases, it might be possible to move the code that fill the dictionary to the main assembly if that assembly knows all needed types because it reference all assemblies that define such types. Otherwise, you need reflection or dependency injection containers. – Phil1970 Jan 30 '17 at 22:09
0

Would something like fit what you are wanting?

class Program {
    static void Main(string[] args) {
        var method = typeof(SomeClass).GetMethod("ReadMessage");
        var readMessage = method.MakeGenericMethod(System.Reflection.Assembly.GetExecutingAssembly().GetTypes().First(p => p.Name == "MyMessageBase01"));

        var o = new SomeClass();

        Console.WriteLine(readMessage.Invoke(o, null));
    }        
}

public class SomeClass {
    public string ReadMessage<T>() {
        return typeof(T).FullName;
    }
}

public class MyMessageBase01 {
}

This will find the first class in your current assembly named "MyMessageBase01" and use that to construct a method for ReadMessage();

To use in production you want want to be more judicious than just finding the first type of that name, and you would want to store the string to type lookup in a dictionary so you don't have to reflect every type in the assembly every time.

Tim
  • 5,940
  • 1
  • 12
  • 18
  • I can't run `typeof(ReadMessage<>)`. I assume since it's a public function to the `NetworkMessage` class. – ShitakeOishii Jan 30 '17 at 21:18
  • I updated my example to show how to invoke a generic method using reflection and finding a type by name, rather than construct a generic class. – Tim Jan 30 '17 at 21:33
  • This wasn't 100% what I needed, but pointed me to the right direction. I'll post an answer of my own to show how I did it. – ShitakeOishii Jan 30 '17 at 23:10
0

You can use Type.GetType(string) from System.Reflection.

Take a look at this link for more info.

Community
  • 1
  • 1
Rafael Costa
  • 1,291
  • 2
  • 13
  • 30
0

@Tim's answer pointed me in the right direction, namely: How do I use reflection to call a generic method? and C#:System.Reflection.MethodInfo cause : (Object does not match target type).

The code snippet that solved the issue:

// Type.EmptyTypes is very crucial, because there are two methods with
// the same `ReadMessage` name. The one that doesn't take parameters
// was the needed one.
MethodInfo method = typeof(NetworkMessage).GetMethod ("ReadMessage",Type.EmptyTypes,null);
MethodInfo generic = method.MakeGenericMethod (*messageBaseTypes*);

// In the referred thread `this` was passed instead of `netMsg`
// However, an instance of the object is needed, else
// a `TargetException : Object does not match target type` is thrown.
var msg = generic.Invoke (netMsg, null);
Community
  • 1
  • 1
ShitakeOishii
  • 89
  • 2
  • 9