Search code examples
c#reflectionjson.net

C# InvokeMethod fail after Newtonsoft serialize


I'm invoking a method dynamically in C# by using the Type.InvokeMember method call.
I have 2 methods, one that accepts a string argument, and one that accepts an int argument.
The code works fine when initialized in code, but fails after Newtonsoft.Json serialization.
Debugging doesn't help since the types seem to be correct. Here is the complete code:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace DynamicMethodCall
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Command> commands = new List<Command>()
            {
                new Command() { ClassName="Module1", MethodName="Method1", Parameters= new object[] { "1" } },
                new Command() { ClassName="Module1", MethodName="Method2", Parameters= new object[] { 1 } }
            };

            foreach (var command in commands)
            {
                string result = command.Invoke();
                Console.WriteLine(result);
            }

            File.WriteAllText("commands.json", JsonConvert.SerializeObject(commands));
            commands = JsonConvert.DeserializeObject<List<Command>>(File.ReadAllText("commands.json"));

            foreach (var command in commands)
            {
                string result = command.Invoke();
                Console.WriteLine(result);
            }
            Console.ReadLine();
        }
    }

    public class Command
    {
        public string ClassName { get; set; }
        public string MethodName { get; set; }
        public object[] Parameters { get; set; }

        public string Invoke()
        {
            try
            {
                Type type = Type.GetType("DynamicMethodCall." + ClassName);
                object instance = Activator.CreateInstance(type);
                string response = (string)type.InvokeMember(MethodName,
                    BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance,
                    null, instance, Parameters);
                return response;
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }
    }

    public class Module1
    {
        public string Method1(string x)
        {
            return $"You called Method1 in Module1 with arg {x}.";
        }

        public string Method2(int x)
        {
            return $"You called Method2 in Module1 with arg {x}.";
        }
    }
}


It returns

  • You called Method1 in Module1 with arg 1.
  • You called Method2 in Module1 with arg 1.
  • You called Method1 in Module1 with arg 1.
  • Method 'DynamicMethodCall.Module1.Method2' not found.

Meaning that after Newtonsoft serialize-deserialize, the method with int argument doesn't work. Anybody know what seems to be the problem?


Solution

  • You're expecting that a JSON numeric value will be deserialized as int - because that's what it was to start with. In reality, it's being deserialized as a long. I believe that's just "what Json.NET does for numbers with no decimal point". (I can't see an option to change that, and I'm not sure it would make sense for there to be one.)

    You can observe that by adding this line before and after deserialization:

    Console.WriteLine(commands[1].Parameters[0].GetType());
    

    You say that: "Debugging doesn't help since the types seem to be correct." - but in this case they aren't correct, because you're ending up trying to call the method with a long value.

    Fundamentally, you've serialized less information than you originally had - because you aren't serializing the type information. Although Json.NET does have a TypeNameHandling setting that helps with this in some cases, I haven't managed to persuade it to simply propagate the actual types (so that it could then deserialize).

    Alternatively, you could use a custom converter to deserialize to int instead. But of course, if you started with a long, that would be just as incorrect...