Вызов методов с необязательными параметрами через отражение - PullRequest
55 голосов
/ 11 марта 2010

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

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

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

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

(я только что попробовал с другим BindingFlags).

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

Ответы [ 5 ]

124 голосов
/ 29 марта 2012

Согласно 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);
23 голосов
/ 11 марта 2010

Необязательные параметры обозначаются обычным атрибутом и обрабатываются компилятором.
Они не влияют (кроме флага метаданных) на IL и не поддерживаются отражением напрямую (за исключением свойств IsOptional и DefaultValue).

Если вы хотите использовать дополнительные параметры с отражением, вам нужно вручную передать их значения по умолчанию.

3 голосов
/ 20 марта 2015

Я просто добавлю немного кода ... потому что. Код не очень приятный, я согласен, но он довольно прост. Надеюсь, это поможет кому-то, кто споткнется через это. Он протестирован, хотя, вероятно, не так хорошо, как хотелось бы в производственной среде:

Вызов метода 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).

2 голосов
/ 04 апреля 2016

Все вопросы исчезают, когда вы видите декомпилированный код:

C #:

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

MSIL:

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

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

1 голос
/ 17 апреля 2011

С фреймворком с открытым исходным кодом ImpromptuInterface начиная с версии 4 вы можете использовать DLR в C # 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"))
...