Matthew Strawbridge
Matthew Strawbridge

Reputation: 20640

How do you call a generic method with out parameters by reflection?

Suppose I have a class like this, containing a generic method with an out parameter:

public class C
{
    public static void M<T>(IEnumerable<T> sequence, out T result)
    {
        Console.WriteLine("Test");
        result = default(T);
    }
}

From reading the answers to a couple of other questions (How to use reflection to call generic Method? and Reflection on a static overloaded method using an out parameter), I thought I might be able to invoke the method via reflection as follows:

// get the method
var types = new[] { typeof(IEnumerable<int>), typeof(int).MakeByRefType() };
MethodInfo mi = typeof(C).GetMethod(
    "M", BindingFlags.Static, Type.DefaultBinder, types, null);

// convert it to a generic method
MethodInfo generic = mi.MakeGenericMethod(new[] { typeof(int) });

// call it
var parameters = new object[] { new[] { 1 }, null };
generic.Invoke(null, parameters);

But mi is coming back null. I've tried using object instead of int in the types array but that doesn't work either.

How can I specify the types (needed for the out parameter) for a generic method before the call to MakeGenericMethod?

Upvotes: 3

Views: 3769

Answers (4)

AakashM
AakashM

Reputation: 63378

I'm still interested to know what the syntax is for specifying an array of template types, or if it's not possible

I don't think it's possible to pass that kind of detailed type specification to GetMethod[s]. I think if you have a number of such Ms to look through, you have to get them all and then filter by the various properties of the MethodInfos and contained objects, eg as much of this as is necessary in your particular case:

var myMethodM =
    // Get all the M methods
    from mi in typeof(C).GetMethods()
    where mi.Name == "M"

    // that are generic with one type parameter
    where mi.IsGenericMethod
    where mi.GetGenericArguments().Length == 1
    let methodTypeParameter = mi.GetGenericArguments()[0]

    // that have two formal parameters
    let ps = mi.GetParameters()
    where ps.Length == 2

    // the first of which is IEnumerable<the method type parameter>
    where ps[0].ParameterType.IsGenericType
    where ps[0].ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
    where ps[0].ParameterType.GetGenericArguments()[0] == methodTypeParameter

    // the second of which is ref <the method type parameter>
    where ps[1].ParameterType.IsByRef
    where ps[1].ParameterType.GetElementType() == methodTypeParameter

    select mi;

Upvotes: 3

Thomas Levesque
Thomas Levesque

Reputation: 292675

This is a well known-problem; to find the method, you need to know its type parameter, but you can't know its type parameter without knowing the method first...

An obvious but inelegant solution is to loop through all methods until you find the right one.

Another option is to take advantage of the Linq Expression API:

public static MethodInfo GetMethod(Expression<Action> expr)
{
    var methodCall = expr.Body as MethodCallExpression;
    if (methodCall == null)
        throw new ArgumentException("Expression body must be a method call expression");
    return methodCall.Method;
}


...

int dummy;
MethodInfo mi = GetMethod(() => C.M<int>(null, out dummy));

Upvotes: 1

terrybozzio
terrybozzio

Reputation: 4532

This will let you call the method:

MethodInfo mi = typeof(C).GetMethod("M");
MethodInfo generic = mi.MakeGenericMethod(new[] { typeof(int) });
var parameters = new object[] { new[]{1},null};
generic.Invoke(null, parameters);

And to get the out parameter:

Console.WriteLine((int)parameters[1]); //will get you 0(default(int)).

Upvotes: 4

SLaks
SLaks

Reputation: 888077

You've passed parameters that will find M<T>(IEnumerable<int>, ref int).
You need to find M(IEnumerable<T>, ref T) (the distinction between ref and out exists only in the C# language; reflection only has ref).

I'm not sure how to pass that; you may need to loop through all methods to find it.

On an unrelated note, you need to pass more BindingFlags:

BindingFlags.Public | BindingFlags.Static

Upvotes: 3

Related Questions