2010-03-11 13 views
49

He tenido otro problema al usar C# 4.0 con parámetros opcionales.Invocando métodos con parámetros opcionales a través de la reflexión

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

Este es el código que uso ahora:

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

(sólo he intentado con diferentes BindingFlags).

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

Respuesta

106

Según MSDN, utilizar el parámetro por defecto 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, p. Ej.

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

¡Esta respuesta es mejor que la marcada como la correcta! –

+0

Puedo dar fe de que esto funciona. Estoy de acuerdo en que esta es una solución mejor en la mayoría de los casos y probablemente deba marcarse como tal. – N8allan

+0

¿Se puede simplemente llamar a "Invocar" (sin parámetros) en el 'MethodInfo' o' ConstructorInfo' resultante? – Alxandr

22

Los parámetros opcionales se denotan mediante un atributo ordinario y son manejados por el compilador.
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).

Si desea utilizar parámetros opcionales con reflexión, tendrá que pasar manualmente sus valores predeterminados.

+0

Gracias. Se me ocurrió una solución que hace exactamente eso. No se ve muy bonito, pero funciona. Y también, IsOptional no es la única propiedad, DefaultValue también existe, así que solo construyo una matriz de todos los valores predeterminados. – Alxandr

1

Con el marco de código abierto ImpromptuInterface partir de la versión 4 se puede utilizar el DLR en C# 4.0 para invocar constructores en un very late bound way y es totalmente consciente de constructores con argumentos con nombre/opcionales, esto corre 4 veces más rápido que Activator.CreateInstance(Type type, params object[] args) y usted don' t 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")) 
3

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. La prueba se hace, aunque probablemente no tan bien como le gustaría que en un entorno de producción:

Llamando al método methodName en el objeto obj con argumentos 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); 
    } 

Y los ArgumentListMatches función está por debajo, que básicamente toma la lugar de la lógica, probablemente, que se encuentra 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 rendimiento!

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

1

Todas las preguntas desaparecen a medida que vea su código decompiled:

C#:

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

msil:

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

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

Cuestiones relacionadas