Подтвердить что ты не робот

Вызов методов с необязательными параметрами посредством отражения

Я столкнулся с другой проблемой, используя С# 4.0 с дополнительными параметрами.

Как мне вызвать функцию (или, скорее, конструктор, у меня есть объект ConstructorInfo), для которого я знаю, что он не требует каких-либо параметров?

Вот код, который я использую сейчас:

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

(Я только что пробовал с разными BindingFlags).

GetParameterlessConstructor - это специальный метод расширения, который я написал для Type.

4b9b3361

Ответ 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 

Как вы видите, необязательный параметр представляет собой реальный отдельный объект, который украшен определенными атрибутами и должен соблюдаться соответствующим образом при вызове посредством отражения, как описано выше.