Get the name of a method using an expression
Asked Answered
A

6

30

I know there are a few answers on the site on this and i apologize if this is in any way duplicate, but all of the ones I found does not do what I am trying to do.

I am trying to specify method info so I can get the name in a type safe way by not using strings. So I am trying to extract it with an expression.

Say I want to get the name of a method in this interface:

public interface IMyInteface
{
    void DoSomething(string param1, string param2);
}

Currently I can get the name using THIS method:

 MemberInfo GetMethodInfo<T>(Expression<Action<T>> expression)
 {
        return ((MethodCallExpression)expression.Body).Method;
 }

I can call the helper method as follows:

var methodInfo = GetMethodInfo<IMyInteface>(x => x.DoSomething(null, null));
Console.WriteLine(methodInfo.Name);

But I am looking for the version that I can get the method name without specifying the parameters (null, null)

like this:

var methodInfo = GetMethodInfo<IMyInteface>(x => x.DoSomething);

But all attempts fail to compile

Is there a way to do this?

Arvad answered 22/11, 2011 at 10:28 Comment(7)
Do not have VS at the moment, but I think you should try accepting Delegate or Action<string, string> in non-generic alternative. In this case you should be able to pass a method groupErie
If I change the input to Delegate it wont compile and the error is cannot convert method group to delegate. If i change it to Action<string, string> it seems to work but the expression is converted to a UnaryExpression and the method is null and I cant see where to get this from the UnaryExpressionArvad
affter converting.. have you tried var methodInfo = GetMethodInfo<IMyInteface>(DoSomething); instead of var methodInfo = GetMethodInfo<IMyInteface>(x => x.DoSomething);? Also, I still think specifying (default(string), default(string)) - is more readable, and you can support overloading methods in this casePolygyny
I cant use GetMethodInfo(DoSomething) by specifying do something directly i need to have a reference to the instance, but there are no instance, thats why it must be an expression, IMyInterface is never actually called with the concrete instance but only to extract the metdatata. I dont know if i am making senseArvad
very close: get-method-name-using-lambda-expressionSlaty
The returned MethodInfo from your approach is not very correct everytime, see this thread lambda-expression-not-returning-expected-memberinfoSlaty
Update for C# 6.0: use the new nameof operator. Super easy and nets you compile-time checking in case of changes. Example: Console.WriteLine(nameof(DoSomething)); See: linkFinch
E
20
x => x.DoSomething

In order to make this compilable I see only two ways:

  1. Go non-generic way and specify it's parameter as Action<string, string>
  2. Specify Action<string, string> as your target delegate type by yourself: GetMethodInfo<IMyInteface>(x => new Action<string,string>(x.DoSomething))

if you are ok to go with second one, which allows you to omit arguments then you can write your GetMethodInfo method as follows:

    MemberInfo GetMethodInfo<T>(Expression<Func<T, Delegate>> expression)
    {
        var unaryExpression = (UnaryExpression) expression.Body;
        var methodCallExpression = (MethodCallExpression) unaryExpression.Operand;
        var methodInfoExpression = (ConstantExpression) methodCallExpression.Arguments.Last();
        var methodInfo = (MemberInfo) methodInfoExpression.Value;
        return methodInfo;
    }

It works for your interface, but probably some generalization will be required to make this working with any method, that's up to you.

Erie answered 22/11, 2011 at 14:10 Comment(9)
I was hoping for something a little less verbose, but nevertheless, this works just fine, thanks.Arvad
I am curious, and I know it has been like 2 years already so I hope you can still answer: Is there a way to allow syntax like this: GetMethodInfo<MyInterface>(x => x.DoSomething);?Aurita
A note on this approach, this works only in .NET 3.5 and 4.0, not in 4.5. In the latter case, the method call expression is represented differently, and hence parameter order differs. In that case you have to do: var methodInfoExpression = (ConstantExpression)methodCallExpression.Object;. Overall I find this approach too cumbersome.Slaty
@nawfal, expression trees are generated by the compiler. It doesn't matter which .Net version you target. You probably mean that the C#2012 compiler generates a different expression tree than earlier versions.Washington
@Washington no not at all. I did mean that the same C# 5 compiler on .NET 3.5 and 4.5 behaves differently. In fact the frameworks do. The implementation of the nuances is in the library. The compiler just emits the final part, which is not what this difference is about. I was asking you to be careful even about the framework differences.Slaty
Also this fails in derived : base scenarios like this: #6659169. Not nitpicking, just awareness..Slaty
@Slaty The C# 5.0 compiler does behave differently depending on the target framework. If I'm looking at it correctly, for .Net 4.0, it generates Call to Delegate.CreateDelegate, while for .Net 4.5, it's the new MethodInfo.CreateDelegate.Comatulid
@Comatulid spot on. Whoa, I didnt know 4.5 had CreateDelegate on MethodInfo itself, thanks! :) Btw, a hybrid approach is not very difficult to write..Slaty
Update for C# 6.0: use the new nameof operator. Super easy and nets you compile-time checking in case of changes. Example: Console.WriteLine(nameof(DoSomething)); See: linkFinch
M
13

The following is compatible with .NET 4.5:

public static string MethodName(LambdaExpression expression)
{
    var unaryExpression = (UnaryExpression)expression.Body;
    var methodCallExpression = (MethodCallExpression)unaryExpression.Operand;
    var methodCallObject = (ConstantExpression)methodCallExpression.Object;
    var methodInfo = (MethodInfo)methodCallObject.Value;

    return methodInfo.Name;
}

You can use it with expressions like x => x.DoSomething, however it would require some wrapping into generic methods for different types of methods.

Here is a backwards-compatible version:

private static bool IsNET45 = Type.GetType("System.Reflection.ReflectionContext", false) != null;

public static string MethodName(LambdaExpression expression)
{
    var unaryExpression = (UnaryExpression)expression.Body;
    var methodCallExpression = (MethodCallExpression)unaryExpression.Operand;
    if (IsNET45)
    {
        var methodCallObject = (ConstantExpression)methodCallExpression.Object;
        var methodInfo = (MethodInfo)methodCallObject.Value;
        return methodInfo.Name;
    }
    else
    {
        var methodInfoExpression = (ConstantExpression)methodCallExpression.Arguments.Last();
        var methodInfo = (MemberInfo)methodInfoExpression.Value;
        return methodInfo.Name;
    }
}

Check this sample code on Ideone. Note, that Ideone does not have .NET 4.5.

Manual answered 17/11, 2014 at 15:28 Comment(0)
C
4

The problem with this is that x.DoSomething represents a method group. And you have to somehow explicitly specify what delegate type do you want to convert that method group into, so that the correct member of the group can be selected. And it doesn't matter if that group contains only one member.

The compiler could infer that you mean that one, but it doesn't do that. (I think it's this way so that your code won't break if you add another overload of that method.)

Snowbear's answer contains good advice on possible solutions.

Comatulid answered 22/11, 2011 at 14:34 Comment(0)
B
2

This is a new answer to an old question, but responds to the "verbose" complaint of the accepted answer. It requires more code, but the result is a syntax like:

MemberInfo info = GetActionInfo<IMyInterface, string, string>(x => x.DoSomething);

or, for methods with a return value

MemberInfo info = GetFuncInfo<IMyInterface, object, string, string>(x => x.DoSomethingWithReturn);  

where

object DoSomethingWithReturn(string param1, string param2);

Just like the framework provides Action<> and Func<> delegates up to 16 parameters, you have to have GetActionInfo and GetFuncInfo methods that accept up to 16 parameters (or more, although I'd think refactoring is wise if you have methods with 16 parameters). A lot more code, but an improvement in the syntax.

Bright answered 21/4, 2012 at 0:40 Comment(2)
I am hoping less verbose means I can do something like this: GetMethodInfo<MyInterface>(x => x.DoSomething);? I am a little confused how to implement your suggestion though. Can you provide a quick code sample?Aurita
The only way to do it (that I know of) without passing the parameters is to include the parameter types in the generic specification - i.e GetMethodInfo<MyInterface, string, string>(x => x.DoSomething).Bright
P
2

If you are ok with using the nameof() operator you can use the following approach.

One of the benefits is not having to unwrap an expression tree or supply default values or worry about having a non-null instance of the type with the method.

// As extension method
public static string GetMethodName<T>(this T instance, Func<T, string> nameofMethod) where T : class
{
    return nameofMethod(instance);
}

// As static method
public static string GetMethodName<T>(Func<T, string> nameofMethod) where T : class
{
    return nameofMethod(default);
}

Usage:

public class Car
{
    public void Drive() { }
}

var car = new Car();

string methodName1 = car.GetMethodName(c => nameof(c.Drive));

var nullCar = new Car();

string methodName2 = nullCar.GetMethodName(c => nameof(c.Drive));

string methodName3 = GetMethodName<Car>(c => nameof(c.Drive));
Padlock answered 13/5, 2019 at 16:32 Comment(1)
If only you could enforce the usage of nameof() in parameters, instead of just having a string.Fenner
L
1

If your application would allow a dependency on Moq (or a similar library), you could do something like this:

class Program
{
    static void Main(string[] args)
    {
        var methodName = GetMethodName<IMyInteface>(x => new Action<string,string>(x.DoSomething));
        Console.WriteLine(methodName);
    }

    static string GetMethodName<T>(Func<T, Delegate> func) where T : class
    {
        // http://code.google.com/p/moq/
        var moq = new Mock<T>();
        var del = func.Invoke(moq.Object);
        return del.Method.Name;
    }
}

public interface IMyInteface
{
    void DoSomething(string param1, string param2);
}
Ladybird answered 22/11, 2011 at 13:57 Comment(6)
-1, this give the handle to the intermediate anonymous delegate you're creating (to pass to the GetMethodInfo method), not of that of the actual member which you're trying to represent (DoSomething).Slaty
@nawful The original poster asked for the string Name, not the original MethodInfo. I have updated my example to clarify.Ladybird
I worry if it doesnt give the correct result still. Have you tested this?Slaty
Yes, I have tested this.Ladybird
Oh well, I read it wrongly, your edit was unwarranted then :). In one way then this is a better solution than the accepted one, so +1. But the downside is that you require a T instance to work your way. Not very sure which is more performant. Thanks for adding!Slaty
This is quite an overkill: creating a Mock class (expensive!), depending on third party library, relying on real lamda functions instead of expression trees, also overcomplicated usage...Orangeman

© 2022 - 2024 — McMap. All rights reserved.