web-dev-qa-db-fra.com

Appel de méthodes avec des paramètres facultatifs par réflexion

J'ai rencontré un autre problème en utilisant C # 4.0 avec des paramètres facultatifs.

Comment invoquer une fonction (ou plutôt un constructeur, j'ai l'objet ConstructorInfo) pour laquelle je sais qu'elle ne nécessite aucun paramètre?

Voici le code que j'utilise maintenant:

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

(Je viens d'essayer avec différents BindingFlags).

GetParameterlessConstructor est une méthode d'extension personnalisée que j'ai écrite pour Type.

57
Alxandr

Selon MSDN , pour utiliser le paramètre par défaut, vous devez passer Type.Missing.

Si votre constructeur a trois arguments facultatifs, au lieu de passer un tableau d'objets vide, vous passeriez un tableau d'objets à trois éléments où la valeur de chaque élément est Type.Missing, par exemple.

type.GetParameterlessConstructor()
    .Invoke(BindingFlags.OptionalParamBinding | 
            BindingFlags.InvokeMethod | 
            BindingFlags.CreateInstance, 
            null, 
            new object[] { Type.Missing, Type.Missing, Type.Missing }, 
            CultureInfo.InvariantCulture);
124
Matt Varblow

Les paramètres facultatifs sont indiqués par un attribut ordinaire et sont gérés par le compilateur.
Ils n'ont aucun effet (autre qu'un indicateur de métadonnées) sur l'IL et ne sont pas directement pris en charge par la réflexion (à l'exception des propriétés IsOptional et DefaultValue).

Si vous souhaitez utiliser des paramètres facultatifs avec réflexion, vous devrez transmettre manuellement leurs valeurs par défaut.

23
SLaks

Je vais juste ajouter du code ... parce que. Le code n'est pas agréable, je suis d'accord, mais il est assez simple. J'espère que cela aidera quelqu'un qui trébuche à travers cela. Il est testé, mais probablement pas aussi bien que vous le souhaiteriez dans un environnement de production:

Appel de la méthode methodName sur l'objet obj avec des 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);
    }

Et la fonction ArgumentListMatches est ci-dessous, qui remplace essentiellement la logique probablement trouvée dans 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);
    }

Beaucoup de LINQ, et cela n'a pas été testé sur les performances!

En outre, cela ne gérera pas les appels de fonction ou de méthode génériques. Cela rend cela beaucoup plus laid (comme dans les appels GetMethod répétés).

3
Gordon

Toutes les questions disparaissent lorsque vous voyez votre code décompilé:

c #:

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

msil:

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

Comme vous le voyez, le paramètre facultatif est une véritable entité distincte qui est décorée d'attributs spécifiques et doit être respectée en conséquence lors de l'appel via la réflexion, comme décrit précédemment.

2
sotonika

Avec le framework opensource ImpromptuInterface à partir de la version 4, vous pouvez utiliser le DLR en C # 4.0 pour invoquer des constructeurs d'une manière très liée tardivement et il est totalement conscient des constructeurs avec named/optional , cela fonctionne 4 fois plus vite que Activator.CreateInstance(Type type, params object[] args) et vous n'avez pas à refléter les valeurs par défaut.

using ImpromptuInterface;
using ImpromptuInterface.InvokeExt;

...

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

ou

//If you want to call one parameter and need to name it
Impromptu.InvokeConstructor(type, CultureInfo.InvariantCulture.WithArgumentName("culture"))
1
jbtule