通过反射调用带有可选参数的方法

65

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

如何调用一个不需要任何参数的函数(或构造函数,我有ConstructorInfo对象)?

这是我目前使用的代码:

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

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

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

6个回答

142
根据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 - 不好意思,不是的。 - fordareh
3
@Alxandr 至少在 MethodInfo 的情况下,有一个稍微不那么麻烦的重载:yourMethod.Invoke(target, Enumerable.Repeat(Type.Missing, 3).ToArray()),其中 target 在构造函数的情况下可能是 null - drzaus
@drzaus感谢您使用Enumerable.Repeat()提供的非常方便的代码片段,是的,在构造函数和静态方法的情况下,targetnull - Manfred
当每个构造函数的参数计数发生变化时,这种方法就无法工作。@Gordon的答案更加灵活。 - Franck

23

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

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


谢谢。我想出了一个完全符合要求的解决方案。它看起来并不是很漂亮,但它能够正常工作。此外,“IsOptional”不是唯一的属性,还有“DefaultValue”,所以我只需将所有“DefaultValue”构建成一个数组即可。 - Alxandr

3

我只是添加了一些代码...因为。我同意,这段代码不太好看,但它相当简单直接。希望这能帮助那些偶然遇到这个问题的人。虽然已经测试过,但在生产环境中可能没有你想要的那样好:

使用参数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调用)。


2
当您看到自己的代码被反编译时,所有问题都会消失:
c#:
public MyClass([Optional, DefaultParameterValue("")]string myOptArg)

msil:

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

正如您所见,可选参数是一个真正的独立实体,其具有特定属性装饰,并且在通过反射调用时必须得到相应的尊重,就像之前所描述的那样。

1

使用开源框架 ImpromptuInterface的4.0版本,您可以使用C# 4.0中的DLR以非常晚绑定的方式调用构造函数,并且它完全了解具有命名/可选参数的构造函数,这比 Activator.CreateInstance(Type type, params object[] args)运行速度快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"))

0

我知道这是一个旧的帖子,但我想补充一下。 如果您不确定该方法存在多少个参数,可以动态执行以下操作:

var method = obj.GetType().GetMethod("methodName");
int? parameters = method?.GetParameters().Length;
var data = method?.Invoke(prop, (object?[]?)(parameters.HasValue ? Enumerable.Repeat(Type.Missing, parameters.Value).ToArray() : Array.Empty<object>()));

希望这能有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接