5

I am using a library in C# where a method requires that I pass the string name of a target method as a parameter.

I want to avoid using hardcoded strings for obvious reasons, so I will write an intermediate util method that takes a method, gets the name (presumably via reflection) and feeds it into the library method.

I expect the intermediate method to look something like this:

public void CallOtherMethod(???? inputMethod)
{
    string methodName = inputMethod.Name; // This gives me the method without the namespace, right?
    this.CallFinalMethod(methodName);
}

To be called like this:

this.CallOtherMethod(this.SomeOtherMethod);

However, I'm having some trouble figuring out the type required to do this.

How can I correctly define my method?

As a side note, I would happily write this as an extension method to the library, but this doesn't quite work with the way the library behaves.

Vesuvian
  • 697
  • 1
  • 7
  • 22
  • Do you really need the name or is calling the method enough? What are you trying to achieve? – Panagiotis Kanavos Mar 17 '14 at 13:17
  • Is there any particular signature that this library expects? E.g., do all methods have to take 0 parameters and return void? Or is the library able to call any method you give it? – dcastro Mar 17 '14 at 13:22
  • 1
    Does this answer your question? [Pass Method as Parameter using C#](https://stackoverflow.com/questions/2082615/pass-method-as-parameter-using-c-sharp) – Davide Cannizzo Aug 23 '20 at 13:11

4 Answers4

3

Try to use Action or Func like this:

public void CallOtherMethod(Action method)
{
    string methodName = method.Method.Name;
    method.Invoke();
}

 public void AnotherMethod(string foo, string bar)
{
    // Do Something
}

Call:

CallOtherMethod( () => AnotherMethod("foo", "bar") );
dcastro
  • 66,540
  • 21
  • 145
  • 155
Only a Curious Mind
  • 2,807
  • 23
  • 39
  • `methodName` will be set to a compiler-generated name (i.e., `b__0`), not `AnotherMethod`. – dcastro Mar 17 '14 at 13:19
  • 1
    Thanks a ton Lucas! I completely forgot that the methods had return values, so I needed to use Func instead of Action. I had no idea you could use delegates this way. – Vesuvian Mar 17 '14 at 13:47
  • You can pass in parameter this: `CallOtherMethod(Func method)` – Only a Curious Mind Mar 17 '14 at 13:48
  • @Vesuvian That's surprising. It's guaranteed not to work using any version of Microsoft's C# compiler though. – dcastro Mar 17 '14 at 13:50
  • 1
    Doesn't work on Windows. This answer seems to depend on an implementation quirk that can change without notice – Panagiotis Kanavos Mar 17 '14 at 13:54
  • 1
    @Vesuvian Do you really want to write code that only works on a specific compiler? You should either: a) pass in method groups only, or b) use expressions to evaluate lambdas. Retrieving the method name from a lambda without using expressions is risky. – dcastro Mar 17 '14 at 13:54
  • 1
    @Vesuvian Are you use a lambda, as this answer is, or are you passing the named method directly? The latter will result in the method's name being provided here, the former should not. If it did, then it would be *incorrect*. – Servy Mar 17 '14 at 13:57
3

If your library constrains methods to have a certain signature (e.g., 0 parameters, return void), then you can use a delegate with the appropriate signature.

For example, an Action represents a method with 0 parameters that returns void.

public void CallOtherMethodMethodGroup(Action action)
{
    MethodInfo method = action.Method;

    //check that 'action' is not a compiler generated lambda
    if (!method.IsDefined(typeof (CompilerGeneratedAttribute)))
    {
        Console.WriteLine(method.Name);
    }
    else
        throw new InvalidOperationException("Use 'CallOtherMethodExpr' instead.");
}

And call it using a method group:

CallOtherMethodMethodGroup(AnotherMethod);

If your library accepts a method with any kind of signature, then use you'll have to pass in a lambda instead of a method group:

CallOtherMethodExpr(() => AnotherMethod("dummy", "arg"));

But, when using a lambda, you'll have to change the method to accept an Expression<Action> instead of Action. An expression can be interpreted, a delegate can't.

public void CallOtherMethodExpr(Expression<Action> expr)
{
    string methodName;

    if (expr.Body.NodeType == ExpressionType.Call)
    {
        var call = expr.Body as MethodCallExpression;
        methodName = call.Method.Name;
    }
    else
        throw new InvalidOperationException("Expression must contain a method call");

    Console.WriteLine(methodName);
}
Community
  • 1
  • 1
dcastro
  • 66,540
  • 21
  • 145
  • 155
0

As I understand this, the question isn't how to call a method via a delegate, but how to get the name of a method without resorting to magic strings.

Using reflection doesn't make the magic strings go away. You can get all methods of a type with a specific signature, but you still need to know the name of the method you are targeting. A simple rename refactoring will still break your code.

A typical solution in such cases is to pass an Expression pointing to the member you want. This is used extensively in MVVM frameworks to pass the name of any modified parameters

The following method will check if the supplied expression is an actual method call and extract the method's name:

    public static bool TryGetName(Expression<Action> expr, out string memberName)
    {
        memberName = null;
        var body = expr.Body as MethodCallExpression;
        if (body == null)
            return false;
        memberName = body.Method.Name;
        return true;
    }

The method can be called like this:

bool success=TryGetName(()=>someObject.SomeMethod(), out name);

All Action and Func objects inherit from Action, which means that SomeMethod may actually be a function. If the method contains parameters, the call will need some dummy values:

bool success=TryGetName(()=>someObject.SomeMethod(null,"dummy",0), out name);
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
-2

You need Delegate

public void OtherMethod(Action method)
{
   MessageBox.Show("I am raised first.");
   string methodname = method.Method.Name;
   method.Invoke();
}

public void SomeMethod()
{
    some code ...
}

if you use a named method for Calling the OtherMethod

OtherMethod(SomeMethod);

methodName is set to "SomeMethod",
but if you use anonymous methods for Calling it

OtherMethod(() => MessageBox.Show("The main thing ..."));

then you'll get a nasty compiler-generated name like <YourNamespaceName_Load>b__0

Mahdi Tahsildari
  • 13,065
  • 14
  • 55
  • 94