Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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));
like image 824
Brent Clark Avatar asked Jan 30 '15 03:01

Brent Clark


People also ask

How do you declare a generic method?

The syntax for a generic method includes a list of type parameters, inside angle brackets, which appears before the method's return type. For static generic methods, the type parameter section must appear before the method's return type.

What is a generic type parameter?

Generic Methods A type parameter, also known as a type variable, is an identifier that specifies a generic type name. The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.

How do you make a method parameter generic in C#?

You can create your own generic interfaces, classes, methods, events, and delegates. You may create generic classes constrained to enable access to methods on particular data types. You may get information on the types used in a generic data type at run-time by means of reflection.


Video Answer


1 Answers

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.

like image 157
Niels Filter Avatar answered Sep 18 '22 16:09

Niels Filter