C#:按类型获取方法(通用列表)

C#: GetMethod by type (generic list)

我有一个 class,只有很少的通用重载方法。我试图通过其参数类型获得一个特定的参数。当我坚持使用前两个(使用 int 和 string 类型的参数)时,它相对容易做到。但无论我做什么,我都无法让我的程序注意到第三个,用于通用列表。我使用了错误的 Type 参数吗?如果是这样,什么是正确的方法?

/* 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)
    {
    }
}

简短版本:typeof(IEnumerable<>)typeof(IEnumerable<T>) 不同(对于某些 T)。

更长的版本:没有方法 void c.m(IEnumerable<> Ls),只有重载,其中通用参数将是一些特定的 – 存在于 运行 时间 – 由于以下原因需要抖动来创建方法的类型一些引用通用方法实例化的代码。

在您的测试代码中添加对泛型方法的某个实例的调用,然后对该实例执行 GetMethod

考虑以下因素:

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}");
            }
        }
    }
}

产生输出:

M
    Int32
M
    IEnumerable`1

请注意,泛型重载只有一个结果。如果将对 M<char>(…) 的调用添加到 Main,则输出 相同

对于反射只有一种方法,它的参数是否反映了它的 "open generic" 性质,但这与使用开放泛型类型(例如 IEnumerable<> )调用并不完全相同因为开放类型不可实例化。

(我在这里捏造了很多技术细节。查看 typeof(IEnumerable<>)typeof(IEnumerable<int>) 之间的调试器差异很有启发性。)

第三种方法的签名为 m<T>(IEnumerable<T>),但您的示例显示尝试查找具有签名 m(IEnumerable<>) 的方法。

typeof(IEnumerable<T>)typeof(IEnumerable<>)的区别在于,前者是泛型,后者是泛型定义,两者不是一回事。泛型类型由泛型类型定义和泛型类型参数确定。

考虑到这一点,您会想要使用:

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

并替换您将传递到方法中的可枚举类型。

另一方面,如果您事先不知道可枚举的类型,您可以获得泛型方法定义并在需要时制作可用的泛型方法:

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

如果您从 int 和 string 方法中删除通用定义:

    public static void m(int i)
    {
    }

    public static void m(String s)
    {
    }

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

并使用以下行获取所需的通用方法:

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

这样就可以了

/// <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;

}

您将需要这个,以避免错误:

var hasNested = firstParameterType.GenericTypeArguments.Length > 0;

这将获取第一次出现的:

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

并将生成一个您可以像这样使用的方法:

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

//we are relying on implicit casting

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

完整样本:

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);
        }