Llamar al método estático con reflexión

111

Tengo varias clases estáticas en el espacio de nombres mySolution.Macroscomo

static class Indent{    
     public static void Run(){
         // implementation
     }
     // other helper methods
}

Entonces, mi pregunta es ¿cómo será posible llamar a esos métodos con la ayuda de la reflexión?

Si los métodos NO fueran estáticos, entonces podría hacer algo como:

var macroClasses = Assembly.GetExecutingAssembly().GetTypes().Where( x => x.Namespace.ToUpper().Contains("MACRO") );

foreach (var tempClass in macroClasses)
{
   var curInsance = Activator.CreateInstance(tempClass);
   // I know have an instance of a macro and will be able to run it

   // using reflection I will be able to run the method as:
   curInsance.GetType().GetMethod("Run").Invoke(curInsance, null);
}

Me gustaría mantener mis clases estáticas. ¿Cómo podré hacer algo similar con métodos estáticos?

En resumen, me gustaría llamar a todos los métodos Run de todas las clases estáticas que están en el espacio de nombres mySolution.Macros.

Tono Nam
fuente

Respuestas:

150

Como indica la documentación de MethodInfo.Invoke , el primer argumento se ignora para los métodos estáticos, por lo que puede pasar nulo.

foreach (var tempClass in macroClasses)
{
   // using reflection I will be able to run the method as:
   tempClass.GetMethod("Run").Invoke(null, null);
}

Como señala el comentario, es posible que desee asegurarse de que el método sea estático al llamar GetMethod:

tempClass.GetMethod("Run", BindingFlags.Public | BindingFlags.Static).Invoke(null, null);
Sotavento
fuente
4
es posible que desee pasar algunas banderas vinculantes a GetMethod.
Daniel A. White
2
Sin él, BindingFlags.Statices posible que no pueda obtener el método con éxito en primer lugar ...
ErikE
1
Es posible que desee agregar BindingFlags.FlattenHierarchy si el método reside en una clase antecesora.
J. Ouwehand
20

Realmente, realmente, realmente podría optimizar mucho su código pagando el precio de crear el delegado solo una vez (tampoco es necesario crear una instancia de la clase para llamar a un método estático). He hecho algo muy similar, y simplemente guardo en caché un delegado del método "Ejecutar" con la ayuda de una clase auxiliar :-). Se parece a esto:

static class Indent{    
     public static void Run(){
         // implementation
     }
     // other helper methods
}

static class MacroRunner {

    static MacroRunner() {
        BuildMacroRunnerList();
    }

    static void BuildMacroRunnerList() {
        macroRunners = System.Reflection.Assembly.GetExecutingAssembly()
            .GetTypes()
            .Where(x => x.Namespace.ToUpper().Contains("MACRO"))
            .Select(t => (Action)Delegate.CreateDelegate(
                typeof(Action), 
                null, 
                t.GetMethod("Run", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)))
            .ToList();
    }

    static List<Action> macroRunners;

    public static void Run() {
        foreach(var run in macroRunners)
            run();
    }
}

Es MUCHO más rápido de esta manera.

Si la firma de su método es diferente de Action, puede reemplazar las conversiones de tipo y typeof de Action por cualquiera de los tipos genéricos de Action y Func necesarios, o declarar su Delegado y usarlo. Mi propia implementación usa Func para imprimir objetos bonitos:

static class PrettyPrinter {

    static PrettyPrinter() {
        BuildPrettyPrinterList();
    }

    static void BuildPrettyPrinterList() {
        printers = System.Reflection.Assembly.GetExecutingAssembly()
            .GetTypes()
            .Where(x => x.Name.EndsWith("PrettyPrinter"))
            .Select(t => (Func<object, string>)Delegate.CreateDelegate(
                typeof(Func<object, string>), 
                null, 
                t.GetMethod("Print", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)))
            .ToList();
    }

    static List<Func<object, string>> printers;

    public static void Print(object obj) {
        foreach(var printer in printers)
            print(obj);
    }
}
Loudenvier
fuente
0

Prefiero la sencillez ...

private void _InvokeNamespaceClassesStaticMethod(string namespaceName, string methodName, params object[] parameters) {
    foreach(var _a in AppDomain.CurrentDomain.GetAssemblies()) {
        foreach(var _t in _a.GetTypes()) {
            try {
                if((_t.Namespace == namespaceName) && _t.IsClass) _t.GetMethod(methodName, (BindingFlags.Static | BindingFlags.Public))?.Invoke(null, parameters);
            } catch { }
        }
    }
}

Uso...

    _InvokeNamespaceClassesStaticMethod("mySolution.Macros", "Run");

Pero en caso de que esté buscando algo un poco más robusto, incluido el manejo de excepciones ...

private InvokeNamespaceClassStaticMethodResult[] _InvokeNamespaceClassStaticMethod(string namespaceName, string methodName, bool throwExceptions, params object[] parameters) {
    var results = new List<InvokeNamespaceClassStaticMethodResult>();
    foreach(var _a in AppDomain.CurrentDomain.GetAssemblies()) {
        foreach(var _t in _a.GetTypes()) {
            if((_t.Namespace == namespaceName) && _t.IsClass) {
                var method_t = _t.GetMethod(methodName, parameters.Select(_ => _.GetType()).ToArray());
                if((method_t != null) && method_t.IsPublic && method_t.IsStatic) {
                    var details_t = new InvokeNamespaceClassStaticMethodResult();
                    details_t.Namespace = _t.Namespace;
                    details_t.Class = _t.Name;
                    details_t.Method = method_t.Name;
                    try {
                        if(method_t.ReturnType == typeof(void)) {
                            method_t.Invoke(null, parameters);
                            details_t.Void = true;
                        } else {
                            details_t.Return = method_t.Invoke(null, parameters);
                        }
                    } catch(Exception ex) {
                        if(throwExceptions) {
                            throw;
                        } else {
                            details_t.Exception = ex;
                        }
                    }
                    results.Add(details_t);
                }
            }
        }
    }
    return results.ToArray();
}

private class InvokeNamespaceClassStaticMethodResult {
    public string Namespace;
    public string Class;
    public string Method;
    public object Return;
    public bool Void;
    public Exception Exception;
}

El uso es prácticamente el mismo ...

_InvokeNamespaceClassesStaticMethod("mySolution.Macros", "Run", false);
Dynamichael
fuente
2
Tragar cualquier posible excepción suele ser una mala idea.
D Stanley
Cierto. Fue perezoso pero simple. He mejorado mi respuesta.
dynamichael