C# very dynamic invocation

2019-05-10 11:26发布

I want to write this in C#:

SomeUnknownType x;

SuperDuperInvoke(x, "MethodName", param1, param2, param3);

SuperDuperInvoke2(x, "MethodName", "param1String", "param2String", "param3String");

Get some object I know nothing about, a method name, and a list of parameters, and just call the method. SuperDuperInvoke2 assumes the parameters are convertible from string.

I assume something like this is possible using the dynamic framework... I just can't find how...

I know I can do this with Reflection, but it's ugly and annoying...


I'll explain myself a little.

I want to use this for integration testing of some business server. The server has a lot of different components that can handle requests, all loaded into an IoC container. I need to expose some of thees components, mostly for testing, so I want to just receive the name of the component, what method i should call with what parameters and just call it.

4条回答
对你真心纯属浪费
2楼-- · 2019-05-10 12:08

You say that you do not like to use reflection, however, since you mention that you only know the methodname as a string, there's only one way: reflection. It's not so hard as you might think. Here's one way:

EDIT: code updated. It now works with overloaded members and any number of parameters. Provided that you know either the type of the parameters, or the parameters are properly initialized to their respective types, this will work.

public static object InvokeMethod(object o, string MethodName, object[] parameters)
{
    // get the types of the params
    List<Type> paramTypes = new List<Type>();
    foreach (object p in parameters)
    {
        paramTypes.Add(p.GetType());
    }

    try
    {
        // get the method, equal to the parameter types
        // considering overloading
        MethodInfo methodInfo = o.GetType().GetMethod(MethodName, paramTypes.ToArray());

        // and invoke the method with your parameters
        return methodInfo.Invoke(o, parameters);
    }
    catch (MissingMethodException e)
    {
        // discard or do something
    }
}

Note: in your question-text you mention that all params are convertible to string and that you pass the values as strings. But if you have a method like Calc(int) and Calc(long) and you have only a string value to be converted to either of these, you'll need more info about your method because there's no way to know up front which of those methods you should call if all parameters values are stringized.

查看更多
冷血范
3楼-- · 2019-05-10 12:11

I know you wrote that you don't like Reflection but is this really all that ugly?

var result = x.GetType().GetMethod( "MethodName" ).Invoke( x, new object[] { methodParams });

If the method could be overloaded you can't go by name only but also need to know how many parameters you are going to invoke it with. Something like this

var method = x.GetType()
              .GetMethods()
              .First(m => m.Name == "MethodName" && m.GetParameters().Length == 2);
var result = method.Invoke( x, new object[] { methodParams });

This will not work if you also need to match your methodParams by type.

查看更多
Explosion°爆炸
4楼-- · 2019-05-10 12:12

When using the dynamic keyword you do need to know the method name at compile time, however what that actually compiles down into, are DLR API calls with a string constant for the method name. It's certainly possible to call these yourself, where it become tricky is that the dlr performance is dependent on static caching sites created along side these api calls.

The open source framework ImpromptuInterface (found in Nuget) wraps the DLR api with some static invoke methods. It hashes the caching sites, so it's not as fast as the dynamic keyword, but it's at least 2x faster than reflection. The only trick is that if you try to call a void returning method expecting a value it throws an exception when trying to bind. Example implementation:

public static dynamic SuperDuperInvoke(object target, string methodName, params object[] args){
    try{
        return Impromptu.InvokeMember(target, methodName, args);
    }catch(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException){
        Impromptu.InvokeMemberAction(target, methodName, args);
        return null;
    }
}

Since it's open source (apache license) you can always go to the source of InvokeMember etc to help implement your SuperDuperInvoke, if you don't want the dependency.

SuperDuperInvoke2 is more difficult because the DLR is going to try and match the method based on the argument types, it will take into account implicit conversions, but only statically define ones (TryConvert on DynamicObject won't work), so you would then need a proxy that has staticaly defined implicit converts to all your expected types, that can be dangerous watch out for method overloads they will likely be ambiguous to SuperDuperInvoke2.

public static dynamic SuperDuperInvoke2(object target, string methodName, params ConvertableProxy[] args){
    return SuperDuperInvoke(target,methodName,args);
}

public class ConvertableProxy{
    private IConvertible _value;
    public ConvertableProxy(IConvertible value){
        _value =value;
    }

    //Automatically convert strings to proxy
    public static implicit operator ConvertableProxy(string value){
        return new ConvertableProxy(value);
    }

    public static implicit operator bool(ConvertableProxy proxy)
    {
        return proxy._value.ToBoolean(null);
    }

    public static implicit operator int(ConvertableProxy proxy)
    {
        return proxy._value.ToInt32(null);
    }

    public static implicit operator string(ConvertableProxy proxy)
    {
        return proxy._value.ToString(null);
    }

    //.. Add Char, DateTime, etc.


}
查看更多
forever°为你锁心
5楼-- · 2019-05-10 12:14

Without dynamic, you would do this:

public static SuperDuperInvoke(object o, string methodName, params object parameters)
{
    Type t = o.GetType();
    MethodInfo mi = t.GetMethod(methodName);
    if (mi == null) throw new Exception("no such method: " + methodName);
    mi.invoke(mi, o, parameters.Length == 0 ? null : parameters);
}

Now this is fairly naive in that it isn't looking for a specific method. Best bet is to instead get all methods of that name and filter down to the ones that have the right number parameters then filter those down to the ones that have types that are assignable from the given parameter's types and then select the one with the least upcasts.

查看更多
登录 后发表回答