Invoking methods with optional parameters through reflection
Asked Answered
L

6

65

I've run into another problem using C# 4.0 with optional parameters.

How do I invoke a function (or rather a constructor, I have the ConstructorInfo object) for which I know it doesn't require any parameters?

Here is the code I use now:

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

(I've just tried with different BindingFlags).

GetParameterlessConstructor is a custom extension method I wrote for Type.

Leibniz answered 11/3, 2010 at 1:42 Comment(0)
D
143

According to MSDN, to use the default parameter you should pass Type.Missing.

If your constructor has three optional arguments then instead of passing an empty object array you'd pass a three element object array where each element's value is Type.Missing, e.g.

type.GetParameterlessConstructor()
    .Invoke(BindingFlags.OptionalParamBinding | 
            BindingFlags.InvokeMethod | 
            BindingFlags.CreateInstance, 
            null, 
            new object[] { Type.Missing, Type.Missing, Type.Missing }, 
            CultureInfo.InvariantCulture);
Donnie answered 28/3, 2012 at 21:52 Comment(5)
Can you simply call "Invoke" (with no parameters) on the resulting MethodInfo or ConstructorInfo?Leibniz
@Leibniz - no, unforunately notErmin
@Leibniz at least in the case of MethodInfo there's a slightly less inconvenient overload: yourMethod.Invoke(target, Enumerable.Repeat(Type.Missing, 3).ToArray()), where target is probably null in the case of the constructorMarva
@Marva Thank you for the very handy snippet using Enumerable.Repeat(), and yes, in the case of constructors and static methods target is null.Vtarj
This does not work when parameter count changes for each constructor.@Gordon answer is much more flexibleExtrabold
E
23

Optional parameters are denoted by an ordinary attribute and are handled by the compiler.
They have no effect (other than a metadata flag) on the IL, and are not directly supported by reflection (except for the IsOptional and DefaultValue properties).

If you want to use optional parameters with reflection, you'll need to manually pass their default values.

Erasmo answered 11/3, 2010 at 1:47 Comment(1)
Thank you. I came up with a solution that does exactly that. It doesn't look all that pretty, but it works. And also, IsOptional isn't the only property, DefaultValue also exists, so I just build an array out of all the DefaultValues.Leibniz
D
3

I'll just add some code... because. The code isn't pleasent, I agree, but it is fairly straight forward. Hopefully this will help someone who stumbles accross this. It is tested, though probably not as well as you would want in a production environment:

Calling method methodName on object obj with arguments 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);
    }

And the function ArgumentListMatches is below, which basically takes the place of the logic probably found in 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);
    }

Lots of LINQ, and this has not been performance tested!

Also, this will not handle generic function or method calls. That makes this significantly more ugly (as in repeated GetMethod calls).

Discountenance answered 19/3, 2015 at 22:55 Comment(0)
S
2

All questions disappear as you see your code decompiled:

c#:

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

msil:

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

As you see, optional parameter is a real separate entity that is decorated with specific attributes and has to be respected accordingly when invoking via reflection, as described earlier.

Spasmodic answered 4/4, 2016 at 8:0 Comment(0)
N
1

With the opensource framework ImpromptuInterface as of version 4 you can use the DLR in C# 4.0 to invoke constructors in a very late bound way and it's totally aware of constructors with named/optional arguments, this runs 4 times faster than Activator.CreateInstance(Type type, params object[] args) and you don't have to reflect the default values.

using ImpromptuInterface;
using ImpromptuInterface.InvokeExt;

...

//if all optional and you don't want to call any
Impromptu.InvokeConstructor(type)

or

//If you want to call one parameter and need to name it
Impromptu.InvokeConstructor(type, CultureInfo.InvariantCulture.WithArgumentName("culture"))
Noria answered 16/4, 2011 at 23:11 Comment(0)
G
0

I know this is an old thread but just want to add this. If you're not sure how many parameters exist for the method, you can do this dynamically instead:

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>()));

Hope this helps.

Gaudet answered 24/12, 2022 at 13:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.