Specify a parameter to take a generic method expression
Asked Answered
R

1

6

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));
Rave answered 30/1, 2015 at 3:55 Comment(3)
If all you want is the MethodInfo, why can't you just process the type parameter T directly? Why the expression at all?Antirrhinum
Processing the type parameter would give me the type, but I want the method.Rave
In your example, the type involved has only one method. So given the type, the method is trivial to identify. If your type has more than one method, then frankly that's just one more of the many missing details that would make this question comprehensible. It seems this is what is often described as an "XY Problem". I.e. you have some other goal you are trying to achieve and rather than asking how to achieve that goal, you've asked how to do some specific thing you think is the right way to achieve that goal.Antirrhinum
A
1

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.

Anson answered 30/1, 2015 at 12:11 Comment(8)
Thanks. I've seen that post - that's like my Add3 option. Don't really want to specify potentially a dozen fake parameters just to provide a method. However, that is the best option so far.Rave
I see, unfortunately I don't know if you're going to get around a solution without having to provide fake parameters or at least the parameter types. If you do ignore them, you end up ignoring potential overloads. e.g. If you work just on the name "AMethod", you'd have to somehow distinguish which overloaded method you're referring to if there are multiples.Anson
Yeah - I think you're right - in my case we don't permit overloaded methods on these classes, so specifying the method name would be sufficient. But I guess generically, it's too hard for the compiler to cope with.Rave
See the update in my answer. If you're not overloading, you could use basic reflection to get the MethodInfo. This way you don't need to actually "invoke" the method by passing fake parameters. Pass the type and the method name.Anson
That's an option I had not considered mentioning as it provides no compile time checking, which is why I like expressions. I thought it mush be possible to use expressions without specifying parameters or parameter types - as I can understand it as a human. I conclude that the compiler is just not so smart - unless I give it an explicit signature such as Add2. Note that passing expressions doesn't actually invoke (execute) the given method - it's just providing an expression that the consumer can process. That's why it bugs me that I have to provide parameters, that won't get used.Rave
I see, yes you're right, expressions don't actually invoke. My bad. I'm sure you'll be just as excited as I am about the nameof operator coming in c# 6. That way you can pass the second parameter as nameof(cl.AMethod) instead of "AMethod", there you have your compile time checking. I'm excited about the nameof operator for specifying the property name in INotifyPropertyChanged.Anson
Brent, I've edited my post with another potential solution. Have a look and see if this would work in your situation.Anson
Thanks. That's pretty much my Add1 option, but you need to have Expression<Func<>> so you can get the method. The problem with this is you have to specify the generic parameters, which clutters the call horribly - worse than a fake call. It sounds like nameof would provide what I want.Rave

© 2022 - 2024 — McMap. All rights reserved.