Brent Clark
Brent Clark

Reputation: 61

Specify a parameter to take a generic method expression

I would like to specify a parameter that can accept a method without having to specify generic arguments to produce the MethodInfo of the given method.

For example, I'd like to write code like this:

interface IService
{
    object AMethod(int p1, int p2);
}
IThingy<IService>() thingy;
thingy.Add(svc => svc.AMethod);

The closest options I can produce are:

interface IThingy<TService>
{
    void Add1<T0, T1, TResult>(Expression<Func<TService, Func<T0, T1, TResult>>> expression);
    void Add2(Expression<Func<TService, Func<int, int, object>>> expression);
    void Add3(Expression<Action<TService>> expression);
}
thingy.Add1<int, int, object>(svc => svc.AMethod);
thingy.Add2(svc => svc.AMethod);
thingy.Add3(svc => svc.AMethod(0, 0));

Add1 implies many Func overloads, which I'm ok with, but cannot be called without specifying the generic parameters.

Add2 requires no generic parameters, but implies a specific overload for every parameter signature.

Add3 requires a call to the method, including fake parameters.

FYI, I will process the expression to get the MethodInfo of the given method like this:

MemberInfo GetMemberFromExpression<T>(Expression<ActionT>> expression)
{
    return ((MethodCallExpression)expression.Body).Method
}
GetMemberFromExpression(svc => svc.AMethod(0, 0));

Upvotes: 6

Views: 859

Answers (1)

Niels Filter
Niels Filter

Reputation: 4528

Passing methods to expressions with their invoked values

You can use the GetMemberFromExpression method as you have it, and then simply remove the Generic parameter. As follows:

static void Main(string[] args)
{
    var memberInfo1 = GetMemberFromExpression(() => Method1(10, 20));
    var memberInfo2 = GetMemberFromExpression(() => Method2());
    var memberInfo3 = GetMemberFromExpression(() => Method3("string", 15, DateTime.Now));
    Console.WriteLine(memberInfo1.Name);
    Console.WriteLine(memberInfo2.Name);
    Console.WriteLine(memberInfo3.Name);
    Console.Read();
}

public static MemberInfo GetMemberFromExpression(Expression<Action> expression)
{
    return ((MethodCallExpression)expression.Body).Method;
}

public static object Method1(int p1, int p2)
{
    return p1 + p2;
}

public static void Method2()
{
    // No return
}

public static double Method3(string p1, int p2, DateTime p3)
{
    return 10d;
}

You'll see that the GetMemberFromExpression will return MethodInfo of any method you pass it, regardless or parameter type and return type.


Ignoring overloads, invoking on instance and name

If you're not concerned about overloads, you could probably use simple reflection instead of building up Expressions. The nameof operator in c# 6 would be a better approach than passing in the name as a string (compile-time checking).

public static MemberInfo GetMemberInfo(Type type, string methodName)
{
    return type.GetMethod(methodName);
}

Note, theres no validation checks in this method, just to show the concept.

The above method will work for any instance or static methods. Simply pass the instance type / or static class type and the method name as follows:

MyClass cl = new MyClass();
var methodInfo1 = GetMemberInfo(cl.GetType(), "AMethod");
var methodInfo2 = GetMemberInfo(typeof(MyClass), "AStaticMethod");

Here's MyClass with the methods:

class MyClass
{
    public void AMethod(int a, int b)
    {
        // instance method
    }

    public static bool AStaticMethod(bool a, bool b)
    {
        return a & b;  // static method
    }
}

This way you don't pass any parameters because you're simply investigating the definition and not invoking.


Using Expressions with types rather than values

Here's a third option. This way you have:

  • Compile-time checking,
  • No need to pass valued ("fake") parameters,
  • Only the parameter types required (which ensures overloaded methods will work).

Create a class catering for the Action and Func overloads:

public class Method
{
    public static MethodInfo GetInfo<TReturn>(Func<TReturn> method)
    {
        return method.Method;
    }
    public static MethodInfo GetInfo<TP1, TReturn>(Func<TP1, TReturn> method)
    {
        return method.Method;
    }
    public static MethodInfo GetInfo<TP1, TP2, TReturn>(Func<TP1, TP2, TReturn> method)
    {
        return method.Method;
    }    
    //... Continue with some more Func overloads


    public static MethodInfo GetInfo(Action method)
    {
        return method.Method;
    }    
    public static MethodInfo GetInfo<TP1>(Action<TP1> method)
    {
        return method.Method;
    }    
    public static MethodInfo GetInfo<TP1, TP2>(Action<TP1, TP2> method)
    {
        return method.Method;
    }
    //... Continue with some more Action overloads
}

Now you can simply get your MethodInfo as follows:

var methodInfo1 = Method.GetInfo<int, int>(cl.AMethod);
var methodInfo2 = Method.GetInfo<bool, bool, bool>(MyClass.AStaticMethod);

Yes you'll have to create a bunch of overloads in the Method class for Action and Func, but that's a once off thing you'll do and .NET does the same with the Action and Func delegates to cater for all the overloads.

Upvotes: 1

Related Questions