简体   繁体   中英

C#: GetMethod by type (generic list)

I have a class with few generic overloaded methods. I am trying to get a specific one by types of its parameters. It's relatively easy to do, when I stick to the first two (with arguments of type int and string). But no matter what I do I cannot get my program to notice the third one, intended for generic list. Do I use a wrong Type argument? If so what is a correct way?

/* rest of code */
    static void Main(string[] args) {
        MethodInfo method =
            typeof(c).GetMethod("m", new Type[] { typeof(int) });

        Console.WriteLine(method);

        method =
            typeof(c).GetMethod("m", new Type[] { typeof(String) });

        Console.WriteLine(method);

        method =
            typeof(c).GetMethod("m", new Type[] { typeof(IEnumerable<>) });

        Console.WriteLine(method);

        Console.ReadKey();
    }
}

static class c
{
    public static void m<T>(int i)
    {
    }

    public static void m<T>(String s)
    {
    }

    public static void m<T>(IEnumerable<T> Ls)
    {
    }
}

Short version: typeof(IEnumerable<>) is not the same as typeof(IEnumerable<T>) (for some T ).

Longer version: there is no method void cm(IEnumerable<> Ls) , only overloads where the generic parameter will be some specific – existing at run time – type where the jitter has needed to create the method due to some code referencing that instantiation of the generic method.

Add a call, in your test code, to some instance of the generic method and then do a GetMethod for that instance.

Consider the following:

using System.Collections.Generic;
using System.Linq;
using static System.Console;

class Methods {
    public static void M(int x)     {
        // no-op
    }

    public static void M<T>(IEnumerable<T> x)     {
        // no-op
    }
}

class Program {
    static void Main(string[] args)  {
        Methods.M(0);
        Methods.M(new[] { "a", "b" });

        ShowAllM();
    }

    public static void ShowAllM() {
        var tm = typeof(Methods);
        foreach (var mi in tm.GetMethods().Where(m => m.Name == "M"))
        {
            WriteLine(mi.Name);
            foreach (var p in mi.GetParameters())
            {
                WriteLine($"\t{p.ParameterType.Name}");
            }
        }
    }
}

which produces the output:

M
    Int32
M
    IEnumerable`1

Note there is only one result from the generic overload. If a call to M<char>(…) is added to Main then the output is the same .

For reflection there is just one method, are its argument reflects its "open generic" nature, but that isn't quite the same as being callable with an open generic type (eg. IEnumerable<> ) as open types are not instantiatable.

(I've fudged much of the technical details here. It is instruictive to look at the difference in a debugger between typeof(IEnumerable<>) and typeof(IEnumerable<int>) .)

The third method has a signature of m<T>(IEnumerable<T>) but your example shows an attempt to find a method with a signature m(IEnumerable<>) .

The difference between the typeof(IEnumerable<T>) and typeof(IEnumerable<>) is the the former is a generic type and the second is a generic type definition and these are not the same thing. A generic type is determined from both the generic type definition and the generic type arguments.

With that in mind you would want to use:

method =
        typeof(c).GetMethod("m", new Type[] { typeof(IEnumerable<MyType>) });

and substitute the type of enumerable that you will be passing into the method.

On the other hand if you don't know the type of enumerable up front you could get the generic method definition and make the useable generic method when you need it:

methodDef =
        typeof(c).GetMethod("m", new Type[] { typeof(IEnumerable<object>) }).GetGenericMethodDefinition();
method = methodDef.MakeGenericMethod(new Type[] { typeof(MyType) });

If you remove generic defenitions from int and string methods:

    public static void m(int i)
    {
    }

    public static void m(String s)
    {
    }

    public static void m<T>(IEnumerable<T> Ls)
    {
    }

And use following lines to get needed generic method:

method = typeof(c).GetMethods().FirstOrDefault(m => m.IsGenericMethod &&
                    m.GetParameters()[0].ParameterType.GetGenericTypeDefinition()
                        == typeof(IEnumerable<>));

This will do the trick

/// <summary>
/// Will fetch first occurence of IEnumerable<T> method and generate new generic method 
/// <para/>
/// that corresponds to Document type
/// </summary>
/// <param name="Document"></param>
/// <param name="MethodName"></param>
/// <returns></returns>
public static MethodInfo GetAppropriateCollectionGenericMethod(object SourceClass, dynamic Document, string MethodName)
{

    //get all public methods
    var publicMethods = SourceClass.GetType().GetMethods().Where(x => x.Name == MethodName && x.IsGenericMethod);

    //filter out only useful methods
    foreach (var goodMethod in publicMethods)
    {
        var methodParams = goodMethod.GetParameters();
        var firstParameterType = methodParams[0].ParameterType;
        //methods that has arguments like Ienumerable<T>, RepeatedField<T> and so on
        var hasNested = firstParameterType.GenericTypeArguments.Length > 0;
        if (hasNested == true)
        {
            //if we found first method with that name that has as parameter an IEnumerable<T> we are ok
            var genericTypeDef = firstParameterType.GetGenericTypeDefinition();
            if (genericTypeDef == typeof(IEnumerable<>))
            {
                //Recover current document type, even if it's a list of such types
                Type documentType = GetDocumentNestedType(Document);
                //simply create a generic method based on Document inner Type
                return goodMethod.MakeGenericMethod(documentType);
            }
        }
    }

    return null;

}

You will need this, in order to avoid errors:

var hasNested = firstParameterType.GenericTypeArguments.Length > 0;

This will fetch first occurency of:

public static void m<T>(IEnumerable<T> Ls)
{
}

and will generate a method that you can use like that:

var localMethod = GenericReflectionHelper.GetAppropriateCollectionGenericMethod(this, Document, nameof(Insert));

//we are relying on implicit casting

localMethod.Invoke(this, new object[] { Document });

Full sample:

public void Insert<T>(T Document)
        {
            //Valid for Lists and Repeated Fields
            if (Document is IEnumerable)
            {

                MethodInfo localMethod;
                var tuple = Tuple.Create(Document.GetType(), nameof(Insert));
                if (CachedMethodsRedirection.ContainsKey(tuple) == true)
                {
                    localMethod = CachedMethodsRedirection[tuple];
                }
                else
                {
                    localMethod = GenericReflectionHelper.GetAppropriateCollectionGenericMethod(this, Document, nameof(Insert));
                    CachedMethodsRedirection.Add(tuple, localMethod);
                }

                //we are relying on implicit casting
                localMethod.Invoke(this, new object[] { Document });

            }
            else
            {

                DocumentSession.GetCollection<T>().Insert(Document);

            }
        }

public void Insert<T>(IEnumerable<T> Document)
        {
            DocumentSession.GetCollection<T>().Insert(Document);
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM