12

In Unity when using coroutines or InvokeRepeating, you have to give a string with the name of the function you want to call. Though this is a pain if you change the name of that function, since you have to remember to change the coroutines that use it. Is there a cleaner way of doing this?

Currently it looks like this:

InvokeRepeating ("SendChangedValues", SEND_RATE, SEND_RATE);

though it would be nice to have something like

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

Is this possible in c#? Or something else that makes sure I get an error/warning when I change the function's name without changing those strings.

Edit 1: The cleanest thing I could think of is making a const string with the function's name, and putting it just before the function itself. So it's harder to forget to change the string, since it's right there above it, and I also only have to change that one const string to change all the coroutines.

Thanks!

McAden
  • 13,714
  • 5
  • 37
  • 63
The Oddler
  • 6,314
  • 7
  • 51
  • 94
  • 2
    Ive never heard of this, but id suggest you create a string variable to use when you need to use it in an invoke and keep this string next to the corresponding method – Sayse Apr 08 '14 at 18:55
  • Haha, yes, that's also what I just thought of :D I was editing my question while you were writing your comment it seems :P – The Oddler Apr 08 '14 at 18:57
  • You can probably create a custom attribute to decorate your functions with, and that attribute can take a string as identifier that need not change if function name changes. Then have a code that given that string finds a function that is decorated with custom attribute initialized with that string, get the method name, and pass it as parameter to `InvokeRepeating`. It doesn't guarantee in case someone changes the attribute's string, but one should not have the need to do so and thus would reduce the chance of poor code refactor (in theory at least). – LB2 Apr 08 '14 at 18:59
  • 1
    You might be able to use the [Caller Information](http://msdn.microsoft.com/en-us/library/hh534540.aspx) attributes introduced with C# 5.0 for this. It also simplifies implementing `INotifyPropertyChanged` which has a similar problem. You might need to wrap it in a method that will call `InvokeRepeating` for you. – Matt Burland Apr 08 '14 at 19:01
  • 1
    You can wait for c# 6 and `nameof` operator (see this SO [question](http://stackoverflow.com/questions/22881465/what-do-these-new-c-sharp-6-features-do)), or use some (not so orthodox) solutions like expression. – Alessandro D'Andria Apr 08 '14 at 19:05
  • 2
    C# 6.0 is supposed to have a `nameof` operator that should do this, but that doesn't help now. This question http://stackoverflow.com/questions/301809/workarounds-for-nameof-operator-in-c-typesafe-databinding has an answer that suggests `new Action(FunctionName).Method.Name`. You would have to specify an appropriate generic version of `Action` or `Func`. – pmcoltrane Apr 08 '14 at 19:06
  • http://stackoverflow.com/questions/2968352/using-system-reflection-to-get-a-methods-full-name and http://joelabrahamsson.com/getting-property-and-method-names-using-static-reflection-in-c/ might be able to give you some important information in reflection. – OnlineCop Apr 08 '14 at 19:08
  • 2
    About that `nameof`... I don't think it's going to play so well with refactoring and overloading. If you rename one of several overloads, which `nameof` usages get changed? At least with method groups, there's some type information projected immediately afterward, allowing resolution of overloads. – Ben Voigt Apr 08 '14 at 19:14

9 Answers9

10

ahhh.. if it were next the C# version, you could have used the nameof operator.

for now, Does this help your cause?

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

called using:

string methodName = GetFunctionName(SendChangedValues);

you might want to explore different delegate types.. Action, Func etc.

the only problem with above is that for every method signature, you might need to define the right signature/overloads to get name.

Raja Nadar
  • 9,409
  • 2
  • 32
  • 41
  • This works great. Though for functions with parameters I have `string GetName(Action method)` and this is fine. Though when I call this I also have to add the type of the variable: `Helpers.GetName(MethodWithStringParam)`. I was wondering is there's a way around this, so I can just do `Helpers.GetName(MethodWithStringParam)`. It's no bid deal, though would be nice. Thanks! – The Oddler Apr 10 '14 at 19:40
4

Coroutines do allow it IF you aren't passing in an extra parameter. The first example in the documentation shows an overload with the signature of 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, unfortunately, does not have this same overload. You should be able to accomplish it there using reflection though as mentioned in some of the other answers.

McAden
  • 13,714
  • 5
  • 37
  • 63
4

I use the following in my codebase, extrapolate for 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);
  }
}

Use it like this

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

  public void DoSomething1SecondLater(){
    Debug.Log("Welcome to the future");
  }
}
Shorn
  • 19,077
  • 15
  • 90
  • 168
2

I understand your problem and I don't have a golden solution for this.

What might help is using lambda expressions as a way to figure it out like in this blog post.

It describes a way to figure out the method name by passing an Expression that will be evaluated as MethodCallExpression. From there you can extract the relevant information.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
2

I remember doing something similar in another context. It went something like this:

You declare a method of your own, that uses the original method:

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

and then call your method like this

MyInvokeRepeating(someObject.SomeFunction);

Better yet, make an extension method.

lalibi
  • 3,057
  • 3
  • 33
  • 41
2

You can do this via static reflection (in at least .NET 4, maybe 3.5 and below)

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

You just need to know the signature of the function you're calling and replace the Action in the example above accordingly.

Michael
  • 3,099
  • 4
  • 31
  • 40
  • That's waaaaay more complicated than needed. – Ben Voigt Apr 08 '14 at 19:39
  • @BenVoigt I'm not sure I agree that it's any more complicated than writing a wrapper to the InvokeRepeating function that's maintained elsewhere. It's also exactly what's been suggested by a few of the comments (http://joelabrahamsson.com/getting-property-and-method-names-using-static-reflection-in-c/) without wrapping it in a Utility class. – Michael Apr 08 '14 at 19:45
  • @BenVoigt You're right, I would down vote my post if I could. – Michael Apr 08 '14 at 19:51
2

Make an enum out of the coroutines you have:

public enum MyCoroutines
{ 
Coroutine1,
Coroutine2
}

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

This way you have no danger of misspelling the function names plus you can find and replace them easily. It's also very good for playing sounds and animations.

Esa
  • 1,636
  • 3
  • 19
  • 23
1

No lambda expression needed when you're dealing with a method (not a property or field).

You can write an InvokeRepeating wrapper that takes a delegate. Call GetInvocationList on the delegate to get the MethodInfo, which has the name.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
0

Here is my take on the matter, since I've recently stumbled upon this problem in context of UnityEvent listeners, which are also stored and can only be retrieved via the method's name. My approach makes more sense when dealing with event handler with different signatures, but it works for any Action and Invoke as well.

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...
}

As others have mentioned before, the simplest way of retrieving the MethodInfo to get a method's name, is via a delegate type such as Action. I wouldn't want to specify overloads for every delegate type such as Action, Action, Action, because I don't use those parameters. Instead I can just cast whatever method I would pass as a parameter to it's correct type.

Xarbrough
  • 1,393
  • 13
  • 23