使用Reflection调用方法时处理null参数

时间:2011-05-11 18:28:48

标签: c# reflection null

我正在尝试编写将从参数列表中推断出类型的代码,然后调用与这些参数匹配的方法。这非常有效,除非参数列表中包含null值。

我想知道如何使Type.GetMethod调用匹配函数/重载,即使参数列表中有null参数也是如此。

object CallMethodReflection(object o, string nameMethod, params object[] args)
{
    try
    {
        var types = TypesFromObjects(args);
        var theMethod = o.GetType().GetMethod(nameMethod, types);
        return (theMethod == null) ? null : theMethod.Invoke(o, args);
    }
    catch (Exception ex)
    {
        return null;
    }
}
Type[] TypesFromObjects(params object[] pParams)
{
    var types = new List<Type>();
    foreach (var param in pParams)
    {
        types.Add((param == null) ? null : param.GetType());
    }
    return types.ToArray();
}

主要问题行是types.Add((param == null) ? null : param.GetType());,这会导致GetMethod调用在类型数组中以null值失败。

void Function1(string arg1){ }
void Function1(string arg1, string arg2){ }
void Function1(string arg1, string arg2, string arg3){ }
void Function2(string arg1){ }
void Function2(string arg1, int arg2){ }
void Function2(string arg1, string arg2){ }

/*1*/ CallMethodReflection(obj, "Function1", "String", "String"); // This works
/*2*/ CallMethodReflection(obj, "Function1", "String", null); // This doesn't work, but still only matches one overload
/*3*/ CallMethodReflection(obj, "Function2", "String", "String"); // This works
/*4*/ CallMethodReflection(obj, "Function2", "String", null); // This doesn't work, and I can see why this would cause problems

主要是,我正在尝试确定如何更改代码,以便行/*2*/也能正常工作。

8 个答案:

答案 0 :(得分:6)

GetMethod调用有一些覆盖,它使用从Binder类派生的对象。这允许您根据传递的实际参数覆盖默认方法绑定并返回要使用的方法。这基本上是另外两个答案正在做的事情。这里有一些示例代码:

http://msdn.microsoft.com/en-us/library/system.reflection.binder.aspx

答案 1 :(得分:5)

未提及的选项是使用Fasterflect,这是一个旨在使反射任务更容易,更快速(通过IL生成)的库。

要调用给定命名参数字典的方法(或具有应该用作参数的属性的对象),可以调用这样的最佳匹配:

obj.TryCallMethod( "SomeMethod", argsDictionary );
obj.TryCallMethod( "AnotherMethod", new { Foo = "Bar" } );

如果你只有参数值及其排序,你可以使用另一个重载:

obj.TryCallMethodWithValues( "MyMethod", 42, "foo", "bar", null, 2.0 );

PS:您需要从源代码控制中获取最新的位以利用TryCallMethodWithValues扩展。

免责声明:我是Fasterflect项目的撰稿人。

答案 2 :(得分:3)

对于任何null参数,您只能匹配任何引用类型。以下非常简单/天真的代码将适用于您所示的方法,但它不会处理歧义上的异常或使用ref / out参数的更复杂情况或能够将派生类型传递给方法或泛型方法。

如果您使用的是4.0,那么简单地使用动态可能是更好的选择。

object CallMethodReflection(object o, string nameMethod, params object[] args)
{
    try
    {
        var types = TypesFromObjects(args);
        var oType = o.GetType();
        MethodInfo theMethod = null;

        // If any types are null have to perform custom resolution logic
        if (types.Any(type => type == null)) 
        {
            foreach (var method in oType.GetMethods().Where(method => method.Name == nameMethod))
            {
                var parameters = method.GetParameters();

                if (parameters.Length != types.Length)
                    continue;

                //check to see if all the parameters match close enough to use
                bool methodMatches = true;
                for (int paramIndex = 0; paramIndex < parameters.Length; paramIndex++)
                {
                    //if arg is null, then match on any non value type
                    if (args[paramIndex] == null)
                    {
                        if (parameters[paramIndex].ParameterType.IsValueType)
                        {
                            methodMatches = false;
                            break;
                        }
                    }
                    else //otherwise match on exact type, !!! this wont handle things passing a type derived from the parameter type !!!
                    {
                        if (parameters[paramIndex].ParameterType != args[paramIndex].GetType())
                        {
                            methodMatches = false;
                            break;
                        }
                    }
                }

                if (methodMatches)
                {
                    theMethod = method;
                    break;
                }
            }
        }
        else
        {
            theMethod = oType.GetMethod(nameMethod, types);
        }

        Console.WriteLine("Calling {0}", theMethod);
        return theMethod.Invoke(o, args);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Could not call method: {0}, error: {1}", nameMethod, ex.ToString());
        return null;
    }
}

答案 3 :(得分:2)

我认为你必须这样做:

var methods = o.GetType().GetMethods().Where(m => m.Name == methodName);

然后基本上做你自己的重载决议。您可以先尝试现有方法,捕获异常,然后尝试上述方法。

答案 4 :(得分:1)

感谢MSDN link以及一些additional SO discussionan outside forum discussion involving a prominent SO member,我试图实施自己的解决方案,到目前为止对我有用。

我创建了一个继承了Binder类的类,并将我的逻辑放在那里处理潜在的null参数/类型。

object CallMethodReflection(object o, string nameMethod, params object[] args)
{
    try
    {
        var types = TypesFromObjects(args);
        var theMethod = o.GetType().GetMethod(nameMethod, CustomBinder.Flags, new CustomBinder(), types, null);
        return (theMethod == null) ? null : theMethod.Invoke(o, args);
    }
    catch (Exception ex)
    {
        return null;
    }
}
Type[] TypesFromObjects(params object[] pParams)
{
    var types = new List<Type>();
    foreach (var param in pParams)
    {
        types.Add((param == null) ? typeof(void) : param.GetType()); // GetMethod above doesn't like a simply null value for the type
    }
    return types.ToArray();
}
private class CustomBinder : Binder
{
    public const BindingFlags Flags = BindingFlags.Public | BindingFlags.Instance;
    public override MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] matches, Type[] types, ParameterModifier[] modifiers)
    {
        if (matches == null)
            throw new ArgumentNullException("matches");
        foreach (var match in matches)
        {
            if (MethodMatches(match.GetParameters(), types, modifiers))
                return match;
        }
        return Type.DefaultBinder.SelectMethod(bindingAttr, matches, types, modifiers); // No matches. Fall back to default
    }
    private static bool MethodMatches(ParameterInfo[] parameters, Type[] types, ParameterModifier[] modifiers)
    {
        if (types.Length != parameters.Length)
            return false;
        for (int i = types.Length - 1; i >= 0; i--)
        {
            if ((types[i] == null) || (types[i] == typeof(void)))
            {
                if (parameters[i].ParameterType.IsValueType)
                    return false; // We don't want to chance it with a wonky value
            }
            else if (!parameters[i].ParameterType.IsAssignableFrom(types[i]))
            {
                return false; // If any parameter doesn't match, then the method doesn't match
            }
        }
        return true;
    }
}

由于Binder类是一个抽象类,因此您必须覆盖其他一些成员以实际使用此代码,但我的大多数覆盖只是在Type.DefaultBinder对象前面。

public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] matches, object value, CultureInfo culture)
{
    return Type.DefaultBinder.BindToField(bindingAttr, matches, value, culture);
}

答案 5 :(得分:0)

我没有测试它,我认为其他答案要好得多,但我想知道为什么这不会起作用:

foreach (var param in pParams.Where(p => p != null)
{
    types.Add(param.GetType());
}

答案 6 :(得分:0)

您可以通过实现自己的GetMethod来解决问题,该GetMethod遍历对象中的所有方法并确定哪一个是最佳匹配,我希望这会有所帮助。 我使用您提供的示例测试了以下方法,并且它可以正常工作

MethodInfo SmarterGetMethod(object o, string nameMethod, params object[] args)
{
var methods = o.GetType().GetMethods();
var min = args.Length;
var values = new int[methods.Length];
values.Initialize();
//Iterates through all methods in o
for (var i = 0; i < methods.Length; i += 1)
{
    if (methods[i].Name == nameMethod)
    {
        var parameters = methods[i].GetParameters();
        if (parameters.Length == min)
        {
            //Iterates through parameters
            for (var j = 0; j < min; j += 1)
            {
                if (args[j] == null)
                {
                    if (parameters[j].ParameterType.IsValueType)
                    {
                        values[i] = 0;
                        break;
                    }
                    else
                    {
                        values[i] += 1;
                    }
                }
                else
                {
                    if (parameters[j].ParameterType != args[j].GetType())
                    {
                        values[i] = 0;
                        break;
                    }
                    else
                    {
                        values[i] += 2;
                    }
                }
            }
            if (values[i] == min * 2) //Exact match                    
                return methods[i];
        }
    }
}

var best = values.Max();
if (best < min) //There is no match
    return null;
//Iterates through value until it finds first best match
for (var i = 0; i < values.Length; i += 1)
{
    if (values[i] == best)
        return methods[i];
}
return null; //Should never happen
}

答案 7 :(得分:0)

  1. 如果没有参数为NULL,则执行常规方法调用,如果一个为null,则为
  2. 如果至少有一个为null,则采用不同的方法:
  3. 从参数构建参数类型列表:如“int,char,null,int”
  4. 获取具有相同数量的函数名称参数的函数重载
  5. 看看是否只有一个匹配功能,因为如果有2个你无法确定要调用哪个(最难的部分,但我觉得相当简单)
  6. 使用您的参数调用您想出的函数并使用null