get methodinfo from a method reference C#

2020-02-05 12:14发布

问题:

We can use a C# typeof keyword when we want to get Type instance for specified type. But what can I use if I want to get MethodInfo of a method by it's reference?

For example I have a simple console app. It contains Program.Main method. I want to get MethodInfo by using something like methodinfoof(Program.Main). I have this problem because the method names might change, so I cannot just use Type.GetMethodInfo(string MethodName) for that.

I have about 10 000 methods for which I would like to get MethodInfo, so adding any custom attributes or anything else to my methods is not a solution.

回答1:

Slight adaptation of a previously posted answer, but this blog post seems to achieve what you're asking for; http://blog.functionalfun.net/2009/10/getting-methodinfo-of-generic-method.html

Sample usage would be as follows;

var methodInfo = SymbolExtensions.GetMethodInfo(() => Program.Main());

Original answer was to this question; https://stackoverflow.com/a/9132588/5827



回答2:

You can use expression trees for non-static methods. Here is an example.

using System.Linq.Expressions;
using System.Reflection;

public static class MethodInfoHelper
{
    public static MethodInfo GetMethodInfo<T>(Expression<Action<T>> expression)
    {
        var member = expression.Body as MethodCallExpression;

        if (member != null)
            return member.Method;

        throw new ArgumentException("Expression is not a method", "expression");
    }
}

You would use it like this:

        MethodInfo mi = MethodInfoHelper.GetMethodInfo<Program>(x => x.Test());
        Console.WriteLine(mi.Name);

Test() is a member function declared in the Program class.

Use MemberExpression and MemberInfo instead if you want to support property getters and setters.



回答3:

Test class

public class  Foo
{
    public void DoFoo()
    {
        Trace.WriteLine("DoFoo");
    }

    public static void DoStaticFoo()
    {
        Trace.WriteLine("DoStaticFoo");
    }
}

And you can do something like this

MethodInfo GetMethodInfo(Action a)
{
    return a.Method;
}

var foo = new Foo();
MethodInfo mi = GetMethodInfo(foo.DoFoo);
MethodInfo miStatic = GetMethodInfo(Foo.DoStaticFoo);

//do whatever you need with method info

Update
Per @Greg comment if you have some parameters to the methods, you can use Action<T>, Action<T1, T2>, Action<T1, T2, T3>, or Func<T1>, the inconvenience is that you will still need to write the overloads for GetMethodInfo.



回答4:

I know this is a very old post, but I'll just throw this out there for someone who might still be looking for a simple solution to this.. It just seems like no one thought of the simplest solution:

typeof(Program).GetMethods();

Returns an array with the MethodInfo of all methods in the Program class, regardless of attributes or of having parameters or not.

You can iterate ove it if you want, for instance, to list the names of all your 10.000+ methods.

You can also do typeof(Program).GetMethod(nameof(Program.Main)); this way if the method's name change Visual Studio's refactoring will rename it here too.

NOTE: The "nameof" keyword was not available 5 years ago when question was posted.



回答5:

Let me add some explanations to the problem at hands here. We are looking for a method GetMethodInfo(SomeMethodSymbol) which returns information about the given method. This is not straitforward because methods may be overloaded in C#. So basically you need to provide additionnal cues into the call to make the compiler (and other code analysers like Intellisense) understand which method you are talking about.

Say for example I am looking for informations about the Math.Abs method. I must then specify which overloaded version of the method I am looking for exactly:

// int
MethodInfo info1 = ((Func<int, int>)Math.Abs).Method;

// or double ?
MethodInfo info2 = ((Func<double, double>)Math.Abs).Method;

Even if there is just one existing overload, like for the Math.Exp method, I must still provide typing cues, because the method might be overloaded in the future and the code would then not compile anymore.

Direct helpers

Given the above remarks, we can provide the following set of helper methods to alleviate somewhat the tedious task of casting every method to reach its informations:

public static class GetMethodInfoUtil
{
    // No cast necessary
    public static MethodInfo GetMethodInfo(Action action) => action.Method;
    public static MethodInfo GetMethodInfo<T>(Action<T> action) => action.Method;
    public static MethodInfo GetMethodInfo<T,U>(Action<T,U> action) => action.Method;
    public static MethodInfo GetMethodInfo<TResult>(Func<TResult> fun) => fun.Method;
    public static MethodInfo GetMethodInfo<T, TResult>(Func<T, TResult> fun) => fun.Method;
    public static MethodInfo GetMethodInfo<T, U, TResult>(Func<T, U, TResult> fun) => fun.Method;

    // Cast necessary
    public static MethodInfo GetMethodInfo(Delegate del) => del.Method;
}

You would then use those helpers like this:

var methodInfos = new[] {

    // Static methods
    GetMethodInfo<int, int>(Math.Abs),
    GetMethodInfo<double, double>(Math.Abs),
    GetMethodInfo<long, long, long>(Math.Max),

    // Static void methods
    GetMethodInfo(Console.Clear),
    GetMethodInfo<string[]>(Main),

    // With explicit cast if too many arguments
    GetMethodInfo((Action<string, object, object>)Console.WriteLine),

    // Instance methods
    GetMethodInfo<string, bool>("".StartsWith),
    GetMethodInfo(new List<int>().Clear),
};

Note that type information should still be provided except for void static method taking no arguments like Console.Clear. Also, for instance methods, an actual instance should be used to get the appropriate method, which uses more resources.

Indirect helpers

Now for some corner cases the above helpers won't work. Say the method uses out parameters for example. In those special cases, extracting method informations from lambda expressions becomes handy, and we get back to the solution provided by other posters (code inspiration from here):

public static class GetIndirectMethodInfoUtil
{
    // Get MethodInfo from Lambda expressions
    public static MethodInfo GetIndirectMethodInfo(Expression<Action> expression) 
        => GetIndirectMethodInfo((LambdaExpression)expression);
    public static MethodInfo GetIndirectMethodInfo<T>(Expression<Action<T>> expression) 
        => GetIndirectMethodInfo((LambdaExpression)expression);
    public static MethodInfo GetIndirectMethodInfo<T, TResult>(Expression<Func<TResult>> expression) 
        => GetIndirectMethodInfo((LambdaExpression)expression);
    public static MethodInfo GetIndirectMethodInfo<T, TResult>(Expression<Func<T, TResult>> expression) 
        => GetIndirectMethodInfo((LambdaExpression)expression);

    // Used by the above
    private static MethodInfo GetIndirectMethodInfo(LambdaExpression expression)
    {
        if (!(expression.Body is MethodCallExpression methodCall))
        {
            throw new ArgumentException(
                $"Invalid Expression ({expression.Body}). Expression should consist of a method call only.");
        }
        return methodCall.Method;
    }
}

You would use those like this:

int dummyInt;
var moreMethodInfos = new[]
{
    // Extracted from lambdas
    GetIndirectMethodInfo(() => "".StartsWith("")),
    GetIndirectMethodInfo((string s) => s.StartsWith(s)),
    GetIndirectMethodInfo(() => int.TryParse("", out dummyInt)),
};

Note that type information is still provided indirectly from argument type. Note as well that a dummy argument has been added just to make it possible to use an out parameter.

Complete demo program: https://dotnetfiddle.net/CkS075.



回答6:

Maybe not the ideal way, but it could help:

var callback = typeof(BlogController).GetMethod(nameof(BlogController.GetBlogs));


回答7:

I created a T4 template that creates the needed helper functions to help you with this. It creates a list of functions to get MethodInfo objects from Func<> or Action<> methods.

Copy the following code into a file named GetMethodInfo.tt:

<#@ template language="C#" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Text" #>
using System;
using System.Linq.Expressions;
using System.Reflection;

namespace Tools
{
    public static class GetMethodInfo
    {
<# int max = 12;
for(int i = 0; i <= max; i++) 
{
    var builder = new StringBuilder();

    for(int j = 0; j <= i; j++) 
    {
        builder.Append("T");
        builder.Append(j);
        if(j != i) 
        {
            builder.Append(", ");
        }
    }

    var T = builder.ToString();
#>
        public static MethodInfo ForFunc<T, <#= T #>>(Expression<Func<T, <#= T #>>> expression)
        {
            var member = expression.Body as MethodCallExpression;

            if (member != null)
                return member.Method;

            throw new ArgumentException("Expression is not a method", "expression");
        }

        public static MethodInfo ForAction<<#= T #>>(Expression<Action<<#= T #>>> expression)
        {
            var member = expression.Body as MethodCallExpression;

            if (member != null)
                return member.Method;

            throw new ArgumentException("Expression is not a method", "expression");
        }

<# } #>
    }
}

Notes:

  • please make sure, that the Build Action of the .tt template is set to None
  • you can create more or less functions by setting the max variable to the appropriate setting.