Получить имя переданного метода без использования nameof - PullRequest
0 голосов
/ 26 июня 2018

Легко объявить метод, который принимает имя метода в виде строки:

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

и вызывает его как:

DoSomethingWithMethodName(nameof(SomeClass.SomeMethod));

Я хочу избавиться отnameof и вызовите некоторый другой метод как:

DoSomethingWithMethod(SomeClass.SomeMethod);

, а затем сможете получить имя метода так же, как в примере выше.Это «возможно» сделать, используя какое-то волшебство Expression и / или Func.Вопрос в том, какая подпись должна иметь DoSomethingWithMethod и что она должна делать!

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

Вопрос, кажется, вызывает много путаницы, и ответы предполагают, что я не задавал.Вот намек на то, к чему я стремлюсь, но не могу понять, что правильно.Это для какой-то другой проблемы (для которой у меня есть решение).Я могу объявить:

    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;
            }
        });
    }

, откуда GetPathOfProperty берется из: https://www.automatetheplanet.com/get-property-names-using-lambda-expressions/ и Полное имя свойства

, а затем использовать:

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

, где EqNumberOfStories:

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

Как видите, я передаю свойство, а затем "раскручиваю" его, чтобы добраться до источника.Я хотел бы сделать то же самое, но в более простой настройке, как описано выше.

Ответы [ 2 ]

0 голосов
/ 26 июня 2018

По сути, вам нужно объявить параметр как Func, который соответствует сигнатуре метода, которую вы хотите принять, а затем обернуть его в Expression, чтобы компилятор выдал вам дерево выражений, а не фактическоеделегировать.Затем вы можете пройтись по дереву выражений, чтобы найти MethodCallExpression, из которого вы можете получить имя метода.(Кстати, пример кода в предоставленной вами ссылке будет работать с вызовами методов, так же, как вы хотите, в дополнение к свойствам)

какая подпись должна быть DoSomethingWithMethod

Это зависит от сигнатуры метода, который вы хотели бы принять в качестве параметра.Если какой-то метод выглядит следующим образом:

public MyReturnType SomeMethod(MyParameterType parameter) {}

, тогда подпись DoSomethingWithMethod будет выглядеть следующим образом:

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

Вы также можете сделать его общим, если вы хотите принимать методы с немного отличающимися сигнатурами.(однако, если вы хотите принимать методы с различным числом параметров, вам придется использовать перегрузку, также компилятор C #, вероятно, не будет автоматически разрешать параметры универсального типа в этой ситуации, и вам придется указывать их явно)

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

и что он на самом деле должен делать

Я предполагаю, что этот вопрос на самом деле, как мне получить имя метода в виде строки из дерева выражений?

Есть несколько различных способов сделать это, и это зависит от того, насколько надежным вы хотите, чтобы ваш код был.Учтите, что указанная выше сигнатура метода позволяет передавать делегат, который значительно сложнее, чем просто один вызов метода.Например:

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

Если вы выполните поиск по дереву выражений, сгенерированному из приведенного выше, вы найдете довольно много вызовов методов, а не один.Если вы просто хотите убедиться, что переданный параметр является одиночным вызовом метода, вероятно, проще просто попытаться привести свойство Body выражения к MethodCallExpression

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

Другой вариант - использоватьшаблон посетителя, это особенно полезно, если у вас более сложный сценарий, например, если вы хотите получить список всех имен методов, например, если их несколько, или поддерживает смесь вызовов свойств или методов и т. д.

Для этого параметра создайте класс, который наследует ExpressionVisitor, переопределяет соответствующие методы в базовом классе и где-то сохраняет результаты.Вот пример:

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);
    }
}

вы можете назвать его так:

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

Наконец, чтобы вызвать его, вы не сможете использовать сокращенный режим «группа методов»вызова DoSomethingWithMethod, поскольку компилятор C # не способен автоматически преобразовывать группу методов в дерево выражений (однако он может автоматически преобразовывать ее в обычный делегат, к которому вы привыкли).

вы не можете сделать:

DoSomethingWithMethod(SomeMethod);

вместо этого оно должно выглядеть как лямбда-выражение:

DoSomethingWithMethod(t => SomeMethod(t));

или, если нет параметров:

DoSomethingWithMethod(() => SomeMethod());
0 голосов
/ 26 июня 2018

Вы можете использовать [CallerMemberName] для получения имени вызывающего метода.

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);
}

В приведенном выше примере параметру memberName параметру будет присвоено значение DoProcessing.

Пример вывода

сообщение: что-то произошло.

имя члена: DoProcessing

путь к исходному файлу: C: \ Users \ user \ AppData\ Local \ Temp \ LINQPad5_osjizlla \ query_gzfqkl.cs

номер строки источника: 37

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

...