【问题标题】:Handle null parameters while calling a method using Reflection使用反射调用方法时处理空参数
【发布时间】:2011-05-11 18:28:48
【问题描述】:

我正在尝试编写从参数列表推断类型的代码,然后调用与这些参数匹配的方法。这非常有效,除非参数列表中有 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*/ 行也能正常工作。

【问题讨论】:

  • 您使用的是 .NET 4 吗? .NET 4 已经支持动态关键字,它可以让你调用方法并且方法解析发生在运行时。

标签: c# reflection null


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

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

    如果你只有参数值和它们的顺序,你可以使用另一个重载:

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

    PS:您需要从源代码管理中获取最新位才能利用 TryCallMethodWithValues 扩展。

    免责声明:我是 Fasterflect 项目的贡献者。

    【讨论】:

    • 感谢您的免责声明,但这似乎是一个不错的项目。
    • 谢谢,我很想这样:)
    【解决方案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;
        }
    }
    

    【讨论】:

      【解决方案4】:

      我认为你必须这样做:

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

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

      【讨论】:

      • 同意。因为你得到一个object 的“值”为null,它基本上根本不是一个对象,而且谈论“什么都没有的类型”没有意义。另一种选择是,如Ben 说,如果您的映射失败,请自己进行映射,并检查您是否找到在特定位置具有可为空的参数类型的方法。
      【解决方案5】:

      感谢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);
      }
      

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

        • 然后我会丢失一些类型并且可能会错过匹配项,因为当我需要匹配一个需要 4 个参数的函数时,我的类型列表为 3。
        【解决方案7】:

        您可以通过实现自己的 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
        }
        

        【讨论】:

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

          【讨论】:

            猜你喜欢
            • 2011-01-13
            • 1970-01-01
            • 1970-01-01
            • 2013-04-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多