66 votes

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 optionnels.

Comment faire pour invoquer une fonction (ou plutôt un constructeur, j'ai la fonction ConstructorInfo ) pour lequel je sais qu'il 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);

(J'ai juste essayé avec différents BindingFlags ).

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

143voto

Matt Varblow Points 2106

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

Si votre constructeur a trois arguments optionnels, alors au lieu de passer un tableau d'objets vide, vous passerez 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);

23voto

SLaks Points 391154

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

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

3voto

Gordon Points 514

Je vais juste ajouter un peu de code... parce que. Le code n'est pas plaisant, j'en conviens, mais il est assez simple. J'espère que cela aidera quelqu'un qui tombera dessus. Il est testé, mais probablement pas aussi bien que vous le voudriez dans un environnement de production :

Appel de la méthode methodName sur l'objet obj avec les 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 prend essentiellement la place de 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 les performances n'ont pas été testées !

En outre, ce système ne gère pas les appels de fonctions ou de méthodes génériques. Cela rend le processus beaucoup plus laid (comme dans les appels GetMethod répétés).

2voto

sotonika Points 121

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 pouvez le constater, le paramètre optionnel est une véritable entité distincte qui est décorée avec des attributs spécifiques et doit être respectée en conséquence lors de l'invocation par réflexion, comme décrit précédemment.

1voto

jbtule Points 11159

Avec le cadre open source ImpromptuInterface à partir de la version 4, vous pouvez utiliser le DLR en C# 4.0 pour invoquer les constructeurs dans un fichier de type chemin très tardif de liaison et il est totalement conscient des constructeurs avec des arguments nommés/optionnels, il s'exécute 4 fois plus vite que Activator.CreateInstance(Type type, params object[] args) et vous n'avez pas besoin de 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)

o

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X