通过reflection调用具有可选参数的方法

我遇到了另一个问题使用C#4.0与可选参数。

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

这里是我现在使用的代码:

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

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

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

根据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); 

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

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

我只是添加一些代码…因为。 代码不是pleasent,我同意,但它是相当直接的。 希望这会帮助那些绊倒这个人的人。 它被testing,虽然可能不如你想要在生产环境中:

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

  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中可能find的逻辑:

  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,这还没有经过性能testing!

而且,这不会处理generics函数或方法调用。 这使得这显得更加丑陋(如重复的GetMethod调用)。

使用开源框架ImpromptuInterface作为版本4,您可以使用C#4.0中的DLR以非常晚的方式调用构造函数,并且完全知道具有命名/可选参数的构造函数,这比Activator.CreateInstance(Type type, params object[] args)运行速度快4倍Activator.CreateInstance(Type type, params object[] args) ,您不必反映默认值。

 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")) 

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

C#:

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

MSIL:

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

正如你所看到的,可选参数是一个真正独立的实体,用特定的属性来装饰,并且在通过reflection调用时必须相应地被尊重,如前所述。