Invocar métodos con parámetros opcionales a través de la reflexión

Me he encontrado con otro problema al usar C # 4.0 con parámetros opcionales.

¿Cómo invoco una función (o más bien un constructor, tengo el objeto ConstructorInfo ) para el cual sé que no requiere ningún parámetro?

Aquí está el código que uso ahora:

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

(Acabo de probar con diferentes BindingFlags ).

GetParameterlessConstructor es un método de extensión personalizado que escribí para Type .

De acuerdo con MSDN , para usar el parámetro predeterminado debe pasar Type.Missing .

Si su constructor tiene tres argumentos opcionales, en lugar de pasar una matriz de objetos vacía pasaría una matriz de objetos de tres elementos donde el valor de cada elemento es Type.Missing , por ejemplo

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

Los parámetros opcionales se denotan por un atributo ordinario y son manejados por el comstackdor.
No tienen ningún efecto (que no sea un indicador de metadatos) en el IL, y no son directamente compatibles con la reflexión (a excepción de las propiedades IsOptional y DefaultValue ).

Si desea usar parámetros opcionales con reflection, deberá pasar manualmente sus valores predeterminados.

Voy a agregar un código … porque. El código no es agradable, estoy de acuerdo, pero es bastante directo. Espero que esto ayude a alguien que tropiece con esto. Está probado, aunque probablemente no tan bien como lo desearía en un entorno de producción:

Método de llamada methodName en objeto obj con argumentos args:

  public Tuple 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(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(true, r); } 

Y la función ArgumentListMatches está debajo, que básicamente toma el lugar de la lógica probablemente encontrada en 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); } 

Un montón de LINQ, ¡y esto no ha sido probado!

Además, esto no manejará funciones genéricas o llamadas a métodos. Eso hace que esto sea significativamente más feo (como en las llamadas GetMethod repetidas).

Con el marco de código abierto ImpromptuInterface a partir de la versión 4 puede usar el DLR en C # 4.0 para invocar constructores de una manera muy tardía y es totalmente consciente de constructores con argumentos nombrados / opcionales, esto se ejecuta 4 veces más rápido que Activator.CreateInstance(Type type, params object[] args) y no tiene que reflejar los valores predeterminados.

 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")) 

Todas las preguntas desaparecen cuando ve el código descomstackdo:

do#:

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

msil:

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

Como puede ver, el parámetro opcional es una entidad real separada que está decorada con atributos específicos y debe respetarse en consecuencia cuando se invoca a través de la reflexión, como se describió anteriormente.