简体   繁体   中英

Get the name of the passed method without using nameof

It is easy to declare a method, which takes a method name as a string:

public void DoSomethingWithMethodName(string methodName)
{
    // Do something with the method name here.
}

and the call it as:

DoSomethingWithMethodName(nameof(SomeClass.SomeMethod));

I want to get rid of nameof and call some other method as:

DoSomethingWithMethod(SomeClass.SomeMethod);

and then be able to get the name of the method the same as in the example above. It "feels" possible to do that using some Expression and / or Func sorcery. The question is what signature this DoSomethingWithMethod should have and what it should actually do!

====================================

The question seems to cause a lot of confusion and the answers assume what I did not ask. Here is a hint at what I am aiming but cant' get right. This is for some different problem (for which I have a solution). I can declare:

    private async Task CheckDictionary(Expression<Func<LookupDictionary>> property, int? expectedIndex = null)
    {
        await RunTest(async wb =>
        {
            var isFirst = true;

            foreach (var variable in property.Compile().Invoke())
            {
                // Pass the override value for the first index.
                await CheckSetLookupIndex(wb, GetPathOfProperty(property), variable, isFirst ? expectedIndex : null);
                isFirst = false;
            }
        });
    }

where GetPathOfProperty comes from: https://www.automatetheplanet.com/get-property-names-using-lambda-expressions/ and Fully-qualified property name

and then use:

    [Fact]
    public async Task CommercialExcelRaterService_ShouldNotThrowOnNumberOfStories() =>
        await CheckDictionary(() => EqNumberOfStories, 2);

where EqNumberOfStories is:

    public static LookupDictionary EqNumberOfStories { get; } = new LookupDictionary(new Dictionary<int, string>
    {
        { 1, "" },
        { 2, "1 to 8" },
        { 3, "9 to 20" },
        { 4, "Over 20" }
    });

As you can see, I am passing a property and then "unwinding" it to get to the source. I'd like to do the same but in a simpler setup as described above.

You can use [CallerMemberName] to get the name of the calling method.

public void DoProcessing()
{
    TraceMessage("Something happened.");
}

public void TraceMessage(string message,
        [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
        [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
        [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
    System.Diagnostics.Trace.WriteLine("message: " + message);
    System.Diagnostics.Trace.WriteLine("member name: " + memberName);
    System.Diagnostics.Trace.WriteLine("source file path: " + sourceFilePath);
    System.Diagnostics.Trace.WriteLine("source line number: " + sourceLineNumber);
}

In example above example memberName param will be assigned with value DoProcessing .

Sample output

message: Something happened.

member name: DoProcessing

source file path: C:\\Users\\user\\AppData\\Local\\Temp\\LINQPad5_osjizlla\\query_gzfqkl.cs

source line number: 37

https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute(v=vs.110).aspx

Basically what you have to do is declare the parameter as a Func that matches the method signature you want to accept and then wrap that in an Expression so that the compiler will give you an expression tree rather than an actual delegate. Then you can walk through the expression tree to find a MethodCallExpression from which you can get the method name. (BTW, the sample code in the link you provided will work with method calls too, just like you want, in addition to properties)

what signature this DoSomethingWithMethod should have

It depends on the signature of the method you would like to take as a parameter. If some method looks like:

public MyReturnType SomeMethod(MyParameterType parameter) {}

Then the DoSomethingWithMethod signature would look like:

public void DoSomethingWithMethod(Expression<Func<MyParameterType,MyReturnType>> methodExpression) {}

You can also make it generic incase you want to accept methods with slightly different signatures (however if you want to accept methods with different numbers of parameters you will have to use overloading, also the C# compiler probably won't resolve the generic type parameters automatically in this situation, and you'll have to specify them explicitly)

public void DoSomethingWithMethod<TParam,TReturn>(Expression<Func<TParam,TReturn>> methodExpression) {}

and what it should actually do

I suppose this question is actually, how do I get the method name as a string from the expression tree?

There are a couple of different ways to do this and it depends on how robust you want your code to be. Consider that the above method signature allows for passing a delegate that is significantly more complicated than just a single method call. For example:

DoSomethingWithMethod(t => t.SomeMethod().SomeOtherMethod(5) + AnotherThing(t));

If you search the expression tree generated from the above you will find quite a few method calls not just one. If you just want to enforce that the passed parameter is a single method call, it's probably easier to just try to cast the expression Body property to a MethodCallExpression

public void DoSomethingWithMethod<TParam,TReturn>(Expression<Func<TParam,TReturn>> methodExpression)
{
    if (methodExpression.Body is MethodCallExpression methodCall)
    {
        var methodName = methodCall.Method.Name;
        //...
    }
}

The other option is to use the visitor pattern, this is helpful especially if you have a more complex scenario, such as you would like to retrieve a list of all method names when there are multiple for example, or a support a mixture of property or method calls, etc.

For this option, create a class that inherits ExpressionVisitor and overrides the appropriate methods in the base class and store the results somewhere. Here's an example:

class MyVisitor : ExpressionVisitor
{
    public List<string> Names { get; } = new List<string>();
    protected override Expression VisitMember(MemberExpression node)
    {
        if(node.Member.MemberType == MemberTypes.Method)
        {
            Names.Add(node.Member.Name);
        }
        return base.VisitMember(node);
    }
}

you can call it like this:

var visitor = new MyVisitor();
visitor.Visit(methodExpression.Body);
var methodName = visitor.Names[0];
//...

Finally, to call it, you won't be able to use the shortened "method group" mode of calling DoSomethingWithMethod since C# compiler is not capable of automatically converting a method group to an expression tree (it can however automatically convert it to a regular delegate, which is the notation you are used to).

So you can't do:

DoSomethingWithMethod(SomeMethod);

instead it will have to look like a lambda expression:

DoSomethingWithMethod(t => SomeMethod(t));

or if no parameters:

DoSomethingWithMethod(() => SomeMethod());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM