Получить имя переданного метода без использования nameof

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

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

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


person Konstantin Konstantinov    schedule 25.06.2018    source источник
comment
Вы хотите выполнить метод? Если это так, возможно, вы ищете Func<T, TResult> Делегировать?   -  person Rufus L    schedule 26.06.2018
comment
Нет, я не хочу выполнять метод. Я хочу его имя. Пример как я хочу это назвать приведен выше. Это возможно?   -  person Konstantin Konstantinov    schedule 26.06.2018
comment
Конечно, можно немного поработать с деревьями выражений, но что это даст? Это было бы длиннее, сложнее и медленнее во время выполнения, чем nameof. Уже есть функция, которая делает то, о чем вы просите, а вы отклоняете ее без каких-либо оснований. Почему бы не использовать nameof?   -  person Mike Zboray    schedule 26.06.2018
comment
Дубликат: stackoverflow.com/questions/171970/   -  person abatishchev    schedule 26.06.2018
comment
А также stackoverflow.com/questions/171970/   -  person abatishchev    schedule 26.06.2018
comment
Если это сработает, то оно будет использоваться для модульных тестов. Так что мне все равно, если это на несколько мс медленнее. Однако, если бы я (и многие другие люди) могли бы написать это проще (а DoSomethingWithMethod(SomeClass.SomeMethod) проще, чем DoSomethingWithMethodName(nameof(SomeClass.SomeMethod))) то стоит попробовать даже ценой сложности реализации лежащего в основе метода.   -  person Konstantin Konstantinov    schedule 26.06.2018
comment
Я думаю, что это нечто большее, чем просто добавление сложности. Я также считаю, что это добавит значительных накладных расходов, поскольку вы будете оценивать деревья выражений для каждого метода, который вызывает этот метод. nameof встроен в фреймворк, и должна быть основная причина, по которой кто-то не захочет использовать его и создать свою собственную версию. Я считаю, что отсутствие необходимости набирать nameof не является одним из них.   -  person Mayank    schedule 26.06.2018
comment
@abatishchev Нет. Я не звоню SomeClass.SomeMethod и, следовательно, нет возможности использовать StackTrace, чтобы добраться до верхнего имени, потому что его нет! Выражения могут помочь, но я просто не знаю, какие именно.   -  person Konstantin Konstantinov    schedule 26.06.2018
comment
@Mayank Меня устраивает дополнительная сложность. Можно это сделать или нет? Если да, то как именно? Спасибо.   -  person Konstantin Konstantinov    schedule 26.06.2018
comment
Итак, вы хотите назвать что-то без вызова nameof в каком-то выражении, поэтому вам нужно позвонить, SomeClass.GetMethodName( x=> x.Method)   -  person TheGeneral    schedule 26.06.2018
comment
если бы я мог сделать вам метод с использованием, GetMyName(SomeClass.Method) это помогло бы? потому что я мог бы сократить это до nameof видите, что я там сделал?   -  person TheGeneral    schedule 26.06.2018


Ответы (2)


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

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

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

public MyReturnType SomeMethod(MyParameterType parameter) {}

Тогда подпись DoSomethingWithMethod будет выглядеть так:

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

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

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());
person Dave M    schedule 26.06.2018
comment
Это должно сработать. Спасибо! - person Konstantin Konstantinov; 26.06.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

person Mayank    schedule 25.06.2018
comment
Извините, это не сработает. Мне не нужно получать имя вызывающего метода. Скорее, мне нужно передать метод. Пожалуйста, смотрите пример выше. - person Konstantin Konstantinov; 26.06.2018
comment
@KonstantinKonstantinov ваш вопрос задает следующий C# - Get the name of the calling method without using nameof - person Mayank; 26.06.2018
comment
Для передачи указателя на сам метод вы можете использовать Func, если есть возвращаемый тип, или Action<T>, если его нет. - person Mayank; 26.06.2018
comment
Мои извинения, вы правы. Я обновил вопрос. Образец кода все еще остается. - person Konstantin Konstantinov; 26.06.2018
comment
@KonstantinKonstantinov попробуйте это stackoverflow.com/questions/8225302/ - person Mayank; 26.06.2018
comment
Спасибо за ссылку! - person Konstantin Konstantinov; 26.06.2018