Получить имя функции в С#

В Unity при использовании сопрограмм или InvokeRepeating вы должны указать строку с именем функции, которую вы хотите вызвать. Хотя это боль, если вы меняете имя этой функции, так как вы должны помнить об изменении сопрограмм, которые его используют. Есть ли более чистый способ сделать это?

В настоящее время это выглядит так:

InvokeRepeating ("SendChangedValues", SEND_RATE, SEND_RATE);

хотя было бы неплохо иметь что-то вроде

InvokeRepeating (SendChangedValues.Name(), SEND_RATE, SEND_RATE); //or
InvokeRepeating (functions.GetName(SendChangedValues), SEND_RATE, SEND_RATE);

Возможно ли это в С#? Или что-то еще, что позволяет мне получить сообщение об ошибке/предупреждении, когда я меняю имя функции, не изменяя эти строки.

Изменить 1: Самое чистая вещь, о которой я мог думать, - это создать строку const с именем функции и поставить ее непосредственно перед самой функцией. Так что сложнее забыть изменить строку, так как она находится прямо над ней, и мне также нужно изменить только одну константную строку, чтобы изменить все сопрограммы.

Спасибо!

Ответ 1

ahhh.. Если бы это была следующая версия С#, вы могли бы использовать оператор nameof.

помогает ли это вашему делу?

private static string GetFunctionName(Action method)
{
 return method.Method.Name;
}

называется:

string methodName = GetFunctionName(SendChangedValues);

вам может понадобиться изучить разные типы делегатов.. Action, Func и т.д.

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

Ответ 2

Я использую следующее в своей кодовой базе, экстраполирую для invokerepeating.

public delegate void Task();

public static class MonoBehaviourExtensions {
  public static void InvokeLater(
    this MonoBehaviour b,
    float time,
    Task task)
  {
    b.Invoke(task.Method.Name, time);
  }
}

Используйте его так:

public class MyBehaviour : MonoBehaviour {
  void Start(){
    this.InvokeLater(1f, DoSomething1SecondLater);
  }

  public void DoSomething1SecondLater(){
    Debug.Log("Welcome to the future");
  }
}

Ответ 3

Coroutines действительно позволяют IF не передавать дополнительный параметр. В первом примере в документации показана перегрузка с сигнатурой Coroutine StartCoroutine(IEnumerator routine);:

using UnityEngine;
using System.Collections;

public class Example : MonoBehaviour {
    void Start() {
        print("Starting " + Time.time);
        StartCoroutine(WaitAndPrint(2.0F));
        print("Before WaitAndPrint Finishes " + Time.time);
    }
    IEnumerator WaitAndPrint(float waitTime) {
        yield return new WaitForSeconds(waitTime);
        print("WaitAndPrint " + Time.time);
    }
}

InvokeRepeating, к сожалению, не имеет такой же перегрузки. Вы должны быть в состоянии выполнить его там, используя отражение, хотя, как упоминалось в некоторых других ответах.

Ответ 4

Я понимаю вашу проблему, и у меня нет для этого золотого решения.

Что может помочь использовать лямбда-выражения как способ понять это, как в этом сообщении .

В нем описывается способ определения имени метода путем передачи Expression, который будет оцениваться как MethodCallExpression. Оттуда вы можете извлечь соответствующую информацию.

Ответ 5

Я помню, что делал что-то подобное в другом контексте. Это произошло примерно так:

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

public static void MyInvokeRepeating<T>(Func<T> method)
{
    InvokeRepeating(method.Method.Name, SEND_RATE, SEND_RATE);
}

а затем вызовите свой метод следующим образом

MyInvokeRepeating(someObject.SomeFunction);

Еще лучше, создайте метод расширения.

Ответ 6

Вы можете сделать это с помощью статического отражения (по крайней мере,.NET 4, может быть, 3.5 и ниже)

// This
InvokeRepeating(((MethodCallExpression)((Expression<Action<string>>)(x => x.ToString())).Body).Method.Name, SEND_RATE, SEND_RATE);

// Is the same as
InvokeRepeating("ToString", SEND_RATE, SEND_RATE);

Вам просто нужно знать подпись вызываемой функции и заменить Действие в приведенном выше примере соответственно.

Ответ 7

Сделайте enum из ваших сопроводительных сообщений:

public enum MyCoroutines
{ 
Coroutine1,
Coroutine2
}

InvokeRepeating(MyCoroutines.Coroutine1.ToString(), x, y);

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

Ответ 8

Никакое лямбда-выражение не требуется, когда вы имеете дело с методом (не свойство или поле).

Вы можете написать оболочку InvokeRepeating, которая принимает делегат. Вызовите GetInvocationList делегата, чтобы получить MethodInfo, который имеет имя.

Ответ 9

Вот мой вопрос по этому вопросу, так как я недавно наткнулся на эту проблему в контексте прослушивателей UnityEvent, которые также хранятся и могут быть восстановлены только через имя метода. Мой подход имеет больше смысла при работе с обработчиком событий с различными сигнатурами, но он работает и для любого Action и Invoke.

public void Start()
{
    Invoke(
        methodName : GetMethodName((System.Action)Explode), 
        time: 1f);

    Invoke(
        methodName : GetMethodName((System.Action<int>)HandleMyNumber), 
        time: 2f);
}

public static string GetMethodName(System.Delegate @delegate)
{
    return @delegate.Method.Name;
}

private void Explode()
{
    // Do something Action like...   
}

private void HandleMyNumber(int number)
{
    // Do something Action<int> like...
}

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