我使用带有可选参数的C#4.0遇到了另一个问题。
如何调用函数(或者更确切地说是构造函数,我有ConstructorInfo
对象),我知道它不需要任何参数?
以下是我现在使用的代码:
type.GetParameterlessConstructor()
.Invoke(BindingFlags.OptionalParamBinding |
BindingFlags.InvokeMethod |
BindingFlags.CreateInstance,
null,
new object[0],
CultureInfo.InvariantCulture);
(我刚试过不同的BindingFlags
)。
GetParameterlessConstructor
是我为Type
撰写的自定义扩展方法。
答案 0 :(得分:121)
根据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);
答案 1 :(得分:23)
可选参数由普通属性表示,由编译器处理
它们对IL没有影响(元数据标志除外),反射不直接支持(IsOptional
和DefaultValue
属性除外)。
如果要使用带反射的可选参数,则需要手动传递其默认值。
答案 2 :(得分: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调用中)。
答案 3 :(得分:2)
当您看到代码被反编译时,所有问题都会消失:
C#:
public MyClass([Optional, DefaultParameterValue("")]string myOptArg)
MSIL:
.method public hidebysig specialname rtspecialname instance void .ctor([opt]string myOptArg) cil managed
如您所见,可选参数是一个真实的独立实体,它使用特定属性进行修饰,并且在通过反射调用时必须相应地遵循,如前所述。
答案 4 :(得分:1)
使用从版本4开始的开源框架ImpromptuInterface,您可以使用C#4.0中的DLR来调用very late bound way中的构造函数,并且它完全了解具有命名/可选参数的构造函数,这运行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"))