C#:GetMethod(按类型)(泛型列表)

本文关键字:泛型 列表 类型 GetMethod | 更新日期: 2023-09-27 18:08:19

我有一个类,它包含一些通用重载方法。我正试图通过它的参数类型来获得一个特定的。当我坚持前两个(使用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)
    {
    }
}

C#:GetMethod(按类型)(泛型列表)

简短版本: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}");
            }
        }
    }
}

产生输出:

MInt32MIEnumerable `1

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

对于反射,只有一个方法,它的参数反映了它的"开放泛型"性质,但这与用开放泛型类型(例如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>) });

并替换将要传递到方法中的enumerable的类型。

另一方面,如果你不知道可枚举的类型,你可以获得通用方法定义,并在需要时生成可用的通用方法:

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