Вызов методов с необязательными параметрами посредством отражения
Я столкнулся с другой проблемой, используя С# 4.0 с дополнительными параметрами.
Как мне вызвать функцию (или, скорее, конструктор, у меня есть объект ConstructorInfo
), для которого я знаю, что он не требует каких-либо параметров?
Вот код, который я использую сейчас:
type.GetParameterlessConstructor()
.Invoke(BindingFlags.OptionalParamBinding |
BindingFlags.InvokeMethod |
BindingFlags.CreateInstance,
null,
new object[0],
CultureInfo.InvariantCulture);
(Я только что пробовал с разными BindingFlags
).
GetParameterlessConstructor
- это специальный метод расширения, который я написал для Type
.
Ответы
Ответ 1
В соответствии с 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);
Ответ 2
Дополнительные параметры обозначаются обычным атрибутом и обрабатываются компилятором.
Они не влияют (кроме флага метаданных) на IL и не поддерживаются напрямую отражением (кроме свойств IsOptional
и DefaultValue
).
Если вы хотите использовать необязательные параметры с отражением, вам нужно вручную передать свои значения по умолчанию.
Ответ 3
Я просто добавлю код... потому что. Я не согласен с этим кодом, но он довольно прямолинейный. Надеюсь, это поможет кому-то, кто спотыкается об этом. Он протестирован, хотя, вероятно, не так хорошо, как вы хотели бы в производственной среде:
Вызов метода methodName для объекта obj с аргументами 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:
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).
Ответ 4
С исходной структурой ImpromptuInterface с версии 4 вы можете использовать DLR в С# 4.0 для вызова конструкторов в очень поздно связанный путь, и он полностью знает конструкторы с именованными/необязательными аргументами, это выполняется в 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"))
Ответ 5
Все вопросы исчезают, как вы видите, что ваш код декомпилирован:
С#:
public MyClass([Optional, DefaultParameterValue("")]string myOptArg)
MSIL:
.method public hidebysig specialname rtspecialname instance void .ctor([opt]string myOptArg) cil managed
Как вы видите, необязательный параметр представляет собой реальный отдельный объект, который украшен определенными атрибутами и должен соблюдаться соответствующим образом при вызове посредством отражения, как описано выше.