【问题标题】:Invoking methods with optional parameters through reflection通过反射调用带有可选参数的方法
【发布时间】:2011-01-26 04:36:17
【问题描述】:

我在使用带有可选参数的 C# 4.0 时遇到了另一个问题。

如何调用我知道不需要任何参数的函数(或者更确切地说是构造函数,我有 ConstructorInfo 对象)?

这是我现在使用的代码:

type.GetParameterlessConstructor()
    .Invoke(BindingFlags.OptionalParamBinding | 
            BindingFlags.InvokeMethod | 
            BindingFlags.CreateInstance, 
            null, 
            new object[0], 
            CultureInfo.InvariantCulture);

(我刚刚尝试过不同的BindingFlags)。

GetParameterlessConstructor 是我为Type 编写的自定义扩展方法。

【问题讨论】:

    标签: c# .net reflection invoke default-value


    【解决方案1】:

    可选参数由普通属性表示,由编译器处理。
    它们对 IL 没有影响(除了元数据标志),并且不直接被反射支持(IsOptionalDefaultValue 属性除外)。

    如果你想通过反射使用可选参数,你需要手动传递它们的默认值。

    【讨论】:

    • 谢谢。我想出了一个可以做到这一点的解决方案。它看起来并不那么漂亮,但它确实有效。而且,IsOptional 不是唯一的属性,DefaultValue 也存在,所以我只是从所有 DefaultValues 中构建了一个数组。
    【解决方案2】:

    使用开源框架 ImpromptuInterface 从版本 4 开始,您可以使用 C# 4.0 中的 DLR 来调用 very late bound way 中的构造函数,它完全了解带有命名/可选参数的构造函数,这比 @987654323 快 4 倍@ 并且您不必反映默认值。

    using ImpromptuInterface;
    using ImpromptuInterface.InvokeExt;
    

    ...

    //if all optional and you don't want to call any
    Impromptu.InvokeConstructor(type)
    

    //If you want to call one parameter and need to name it
    Impromptu.InvokeConstructor(type, CultureInfo.InvariantCulture.WithArgumentName("culture"))
    

    【讨论】:

      【解决方案3】:

      根据MSDN,要使用默认参数你应该传递Type.Missing

      如果您的构造函数有三个可选参数,那么您将传递一个三元素对象数组,而不是传递一个空对象数组,其中每个元素的值是Type.Missing,例如

      type.GetParameterlessConstructor()
          .Invoke(BindingFlags.OptionalParamBinding | 
                  BindingFlags.InvokeMethod | 
                  BindingFlags.CreateInstance, 
                  null, 
                  new object[] { Type.Missing, Type.Missing, Type.Missing }, 
                  CultureInfo.InvariantCulture);
      

      【讨论】:

      • 您可以简单地在结果MethodInfoConstructorInfo 上调用“Invoke”(不带参数)吗?
      • @Alxandr - 不,很遗憾没有
      • @Alxandr 至少在 MethodInfo 的情况下,不方便的重载会稍微少一些:yourMethod.Invoke(target, Enumerable.Repeat(Type.Missing, 3).ToArray()),其中 target 在构造函数的情况下可能是 null
      • @drzaus 感谢您使用Enumerable.Repeat() 提供非常方便的sn-p,是的,在构造函数和静态方法的情况下targetnull
      • 当每个构造函数的参数计数发生变化时,这不起作用。@Gordon 答案更加灵活
      【解决方案4】:

      我将添加一些代码...因为。我同意,代码并不令人愉快,但它相当简单。希望这将帮助那些偶然发现这一点的人。它已经过测试,但可能不如您在生产环境中想要的那么好:

      使用参数 args 在对象 obj 上调用方法 methodName:

          public Tuple<bool, object> Evaluate(IScopeContext c, object obj, string methodName, object[] args)
          {
              // Get the type of the object
              var t = obj.GetType();
              var argListTypes = args.Select(a => a.GetType()).ToArray();
      
              var funcs = (from m in t.GetMethods()
                           where m.Name == methodName
                           where m.ArgumentListMatches(argListTypes)
                           select m).ToArray();
      
              if (funcs.Length != 1)
                  return new Tuple<bool, object>(false, null);
      
              // And invoke the method and see what we can get back.
              // Optional arguments means we have to fill things in.
              var method = funcs[0];
              object[] allArgs = args;
              if (method.GetParameters().Length != args.Length)
              {
                  var defaultArgs = method.GetParameters().Skip(args.Length)
                      .Select(a => a.HasDefaultValue ? a.DefaultValue : null);
                  allArgs = args.Concat(defaultArgs).ToArray();
              }
              var r = funcs[0].Invoke(obj, allArgs);
              return new Tuple<bool, object>(true, r);
          }
      

      下面是函数ArgumentListMatches,它基本上取代了GetMethod中可能找到的逻辑:

          public static bool ArgumentListMatches(this MethodInfo m, Type[] args)
          {
              // If there are less arguments, then it just doesn't matter.
              var pInfo = m.GetParameters();
              if (pInfo.Length < args.Length)
                  return false;
      
              // Now, check compatibility of the first set of arguments.
              var commonArgs = args.Zip(pInfo, (margs, pinfo) => Tuple.Create(margs, pinfo.ParameterType));
              if (commonArgs.Where(t => !t.Item1.IsAssignableFrom(t.Item2)).Any())
                  return false;
      
              // And make sure the last set of arguments are actually default!
              return pInfo.Skip(args.Length).All(p => p.IsOptional);
          }
      

      大量的 LINQ,这还没有经过性能测试!

      此外,这不会处理通用函数或方法调用。这使得这变得更加丑陋(就像在重复的 GetMethod 调用中一样)。

      【讨论】:

        【解决方案5】:

        当您看到反编译的代码时,所有问题都会消失:

        c#:

        public MyClass([Optional, DefaultParameterValue("")]string myOptArg)
        

        msil:

        .method public hidebysig specialname rtspecialname instance void .ctor([opt]string myOptArg) cil managed 
        

        如您所见,可选参数是一个真正独立的实体,它用特定的属性进行装饰,并且在通过反射调用时必须相应地加以尊重,如前所述。

        【讨论】:

          猜你喜欢
          • 2016-05-26
          • 2012-04-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-04-25
          • 2014-04-20
          • 2011-12-11
          相关资源
          最近更新 更多