泛型乐趣:其中 typeof(List<T>) != typeof(List<T>),并使用反射获得具有泛型参数的泛型方法

Generics Fun: Where typeof(List<T>) != typeof(List<T>), and using Reflection to get a generic method, with generic parameters

这只是 .NET 的又一天。直到我不得不使用反射进行序列化,获得带有泛型参数的静态 class 的泛型方法。听起来还不错。 GetRuntimeMethod("x", new[] { type }),像往常一样应该可以解决问题,或者我是这么想的。

现在,此方法不断为以下变体返回 null: public static Surrogate<T> BuildSurrogate<T>(List<T> collection)

所以,快速复制到 LinqPad,然后 GetRuntimeMethods 运行,它似乎具有预期的所有方法。很自然地,我想也许 GetRuntimeMethod 的行为有些不对,所以,我快速扩展了 GetRuntimeMethodEx 来迭代,令我惊讶的是,它失败了。什么?怎么会失败。 GetRuntimeMethods 具有我需要的确切 methodInfo。

因此,我最终将扩展程序分解成多个部分以了解到底发生了什么,如下面的代码所示。事实证明 (cType != cGivenType) 总是以 true 结束。

但快速检查显示它们是相同的 'apparent' 类型 - List<T>。现在完全糊涂了,对两个 typeof(List<T>) 的转储进行比较,结果发现它们不一样!

两者的MetadataToken完全一样。然而 RuntimeTypeHandle 是不同的。给定的类型具有正确的 AssemblyQualifiedName 和属于 System.Collections.Generic.List``1, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089FullName。伟大的。但奇怪的是,泛型方法的类型,它们都是“null”!所以,基本上,List<T> 是一个没有对应程序集(!?)的魔法类型。 It exists, but doesn't。多么迷人!

这是两者之间差异的快速转储,这是相关的。

请注意 GenericTypeParametersIsGenericTypeDefinition - 它们似乎非常有道理。除了奇怪之处,现在如何在 MethodInfo 上创建与此类型匹配的类型?潜在地,编译器期望 List<> 的泛型类型和泛型参数 T - 唯一的问题是,你不能从字面上用 T 创建泛型类型。 T 必须是某种东西的类型,它现在使相等性无效。

 private void Main()
    {
        var type = typeof (List<>);
        var m = typeof (Builders).GetRuntimeMethods();
        var surrogateBuilder = typeof (Builders)
                        .GetRuntimeMethodEx("BuildSurrogate", new[] {type});
    }

    static class Builders
    {
        public static Surrogate<T> BuildSurrogate<T>(List<T> collection)
        {
            return new Surrogate<T>
            {
                Items = collection.ToArray(),
            };
        }

        public class Surrogate<T>
        {
            public IEnumerable<T> Items;
        }
    }

    public static class ReflectionExtensions
    {
        public static MethodInfo GetRuntimeMethodEx(
                this Type type, string name, params Type[] types)
        {
            var m = type.GetRuntimeMethods();
            var res = (m.Where(t =>
            {
                var n = name;
                return t.Name.Equals(n);
            }).FirstOrDefault(t =>
            {
                var px = t.GetParameters().ToArray();
                var currentTypes = px.Select(p => p.ParameterType).ToArray();
                if (currentTypes.Length < 1) return false;
                for (var i = 0; i < types.Length; i++)
                {
                    var cGivenType = types[i];
                    for (var j = 0; j < currentTypes.Length; j++)
                    {
                        var cType = currentTypes[j];
                        if (cType != cGivenType) return false;
                    }
                }
                return true;
            }));
            return res;
        }
    }

那是因为你的类型是 GenericTypeDefinition (List<>) 而反映你的 class 的类型是实际的 List<T>.

你的代码不可读,所以我从头开始写了自己的代码。重要的部分在 TypesMatch 方法中。

public static MethodInfo GetRuntimeMethodEx(
        this Type type, string name, params Type[] types)
{
    var withMatchingParamTypes =
        from m in type.GetRuntimeMethods()
        where m.Name == name
        let parameterTypes = m.GetParameters().Select(p => p.ParameterType).ToArray()
        where parameterTypes.Length == types.Length
        let pairs = parameterTypes.Zip(types, (actual, expected) => new {actual, expected})
        where pairs.All(x => TypesMatch(x.actual, x.expected))
        select m;

    return withMatchingParamTypes.FirstOrDefault();
}

private static bool TypesMatch(Type actual, Type expected)
{
    if (actual == expected)
        return true;

    if (actual.IsGenericType && expected.IsGenericTypeDefinition)
        return actual.GetGenericTypeDefinition() == expected;

    return false;
}

Returns 你的方法,符合预期。

您无法创建代表 List<T>T 未知的 Type 实例。这就是 GetGenericTypeDefinitionList<> 的目的。