Home > Software engineering >  Call a generic method with a specific type only known at run time and return that type?
Call a generic method with a specific type only known at run time and return that type?

Time:11-04

I am trying to set up a networked event system in my Unity Project that allows triggering events on other clients. The event also is supposed to pass an EventArgs-type specific to that event.

However, apparently System.EventArgs is not Serializable and thus can not be directly sent over the MLAPI Network. Therefore I convert the class to a string with Unity's JsonUtility class and instead send that string. Additionally, I send another string which is the specific eventArgsType.ToString().

On client side, I am then converting the eventArgsType to a Type with GetType, and would then like to Deserialize the data-string into the correct eventArgs class. Which I currently do by nasty if-else case comparing to the possible eventArgs-Subtypes and then Deserialize as that. That works, but is extremely ugly and will get out of hand the more subtypes I have. But I am not quite sure of how to do that and would appreciate any tips.

What I currently have:

public class EventSystem : NetworkBehaviour
{
       [...]
       public void TriggerLocalAndNetwork(string eventName, EventArgs data)
       {
            ulong clientId = NetworkManager.Singleton.LocalClientId;
            if (Instance._eventDictionary.TryGetValue(eventName, out var triggeredEvent))
            {
                triggeredEvent.Invoke(clientId, data); // Local Trigger

                string argsTyp = data.ToString();
                EventToServerRpc(eventName, clientId, EventArgsSerializer.Serialize(data), argsTyp);
            }
        }

        [ServerRpc(RequireOwnership = false)]
        private void EventToServerRpc(string eventName, ulong clientId, string data, string argsType)
        {
            EventToClientRpc(eventName, clientId, data, argsType);
        }

        [ClientRpc]
        private  void EventToClientRpc(string eventName, ulong clientId, string data, string argsType)
        {
            if (clientId == NetworkManager.Singleton.LocalClientId)
                return;

            if (!Instance._eventDictionary.TryGetValue(eventName, out var triggeredEvent)) 
                return;
            
            Type args = Type.GetType(argsType);
            EventArgs eventArgs = EventArgs.Empty;

            // This is what I want to get rid off
            if (args == typeof(MagicAttackEventArgs))
            {
                eventArgs = EventArgsSerializer.Deserialize<MagicAttackEventArgs>(data);
            }
            else if (args == typeof(HealthSystem.HealthSystem.TakeDamageEventArgs))
            {
                eventArgs = EventArgsSerializer.Deserialize<HealthSystem.HealthSystem.TakeDamageEventArgs>(data);
            }

            triggeredEvent.Invoke(clientId, eventArgs);
        }
}


public static class EventArgsSerializer
{
        public static string Serialize(EventArgs eventArgs)
        {
            string json = JsonUtility.ToJson(eventArgs);
            return json;
        }

        public static T Deserialize<T>(string input)
        { 
            T eventArgs = JsonUtility.FromJson<T>(input);
            return eventArgs;
        }
}

What I tried instead of the if-else part is this:

Type args = Type.GetType(argsType);
MethodInfo method = typeof(EventArgsSerializer).GetMethod(nameof(EventArgsSerializer.Deserialize));
MethodInfo generic = method.MakeGenericMethod(args);
var result = generic.Invoke(null, new object[]{data});
EventArgs eventArgs = (EventArgs) Convert.ChangeType(result, typeof(EventArgs));
triggeredEvent.Invoke(clientId, eventArgs);

But it yield a casting error, and I am not sure if I used the tools correctly at all, it was inspired just inspired by https://stackoverflow.com/a/232621 this answer.

CodePudding user response:

As per the docs there is an overload of that FromJson method which takes a Type

Type args = Type.GetType(argsType);
EventArgs eventArgs = (EventArgs)JsonUtility.FromJson(data,args);

CodePudding user response:

I would suggest using a serialization library with support for polymorphism. See for example the article for System.Text.Json, but this is a rather common feature for most serialization libraries.

This lets you use a common base class for messages that can be used when de-serializing. I would recommend defining your own message-type, rather than just using EventArgs.

There are a few different ways to do different things depending on the message:

  • A virtual method, probably the simplest option, but may be more difficult to use if the method needs different parameters depending on the actual type.
  • pattern matching
  • The visitor pattern
  • Related