【问题标题】:Generics Fun: Where typeof(List<T>) != typeof(List<T>), and using Reflection to get a generic method, with generic parameters泛型乐趣: where typeof(List<T>) != typeof(List<T>),并使用反射获取泛型方法,带有泛型参数
【发布时间】:2015-02-25 22:43:49
【问题描述】:

这只是 .NET 的另一天。直到我不得不使用反射进行序列化来获取具有泛型参数的静态类的泛型方法。听起来没那么糟糕。 GetRuntimeMethod("x", new[] { type }),像往常一样应该做到这一点,或者我是这么想的。

现在,对于以下变体,此方法不断返回 null: public static Surrogate&lt;T&gt; BuildSurrogate&lt;T&gt;(List&lt;T&gt; collection)

所以,快速复制到 LinqPad,稍后运行 GetRuntimeMethods,它似乎具有预期的所有方法。自然地,我想,GetRuntimeMethod 的行为可能有些不对劲,所以,我创建了一个快速扩展,GetRuntimeMethodEx 进行迭代,令我惊讶的是,它失败了。什么?怎么会失败。 GetRuntimeMethods 具有我需要的确切方法信息。

所以,我最终将扩展分解为多个部分,以了解究竟发生了什么,如下面的代码所示。事实证明(cType != cGivenType) 总是以正确的方式结束。

但快速检查显示它们是相同的“明显”类型 - List&lt;T&gt;。现在完全糊涂了,两个typeof(List&lt;T&gt;) 的转储上的差异,事实证明它们不一样!

两者的MetadataToken一模一样。但是RuntimeTypeHandle 不同。给定类型具有正确的AssemblyQualifiedNameFullName,属于System.Collections.Generic.List``1, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089。伟大的。但奇怪的是,泛型方法上的类型,它们都是“null”!所以,基本上,List&lt;T&gt; 是一个没有相应程序集的神奇类型(!?)。 It exists, but doesn't。多么迷人!

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

请注意GenericTypeParametersIsGenericTypeDefinition - 它们似乎很有意义。撇开这些奇怪的事不谈,现在如何在 MethodInfo 上创建一个与该类型匹配的类型?潜在地,编译器期望泛型类型为List&lt;&gt; 和泛型参数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;
        }
    }

【问题讨论】:

  • 这失败是合乎逻辑的,永远不要在你的代码中使用MakeGenericMethod将类型参数 T 绑定到类型参数
  • 没有可以使用 MakeGenericMethod 绑定的类型,因为它仍然是 List&lt;T&gt; 和未知的 T
  • @Bas - 你不能绑定未知数! Edit: @Marcin 打败了我。是的,这就是问题所在。
  • 抱歉,我看错了。但是,它不能与 List&lt;&gt; 一起使用,因为参数需要是 List&lt;T&gt;,这是一种不同的类型。您不能使用相等比较来比较您的“占位符”List&lt;&gt;List&lt;T&gt;。使用答案中描述的方法
  • 得到了答案,正如@Marcin 所述,但是,这很令人困惑,因为这两种类型似乎都显示(几乎)相同的元数据!上图展示了这一点。一个透明的GenericTypeDefinition (List&lt;&gt;) 可以彻底解决它。无论如何,感谢您的快速解决。干杯。 :)

标签: c# .net generics reflection type-equivalence


【解决方案1】:

那是因为您的类型是 GenericTypeDefinition (List&lt;&gt;),而反映您的班级的类型是实际的 List&lt;T&gt;

您的代码不可读,所以我从头开始编写自己的代码。重要的部分在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;
}

按预期返回您的方法。

您不能用未知的T 创建代表List&lt;T&gt;Type 实例。这就是 GetGenericTypeDefinitionList&lt;&gt; 的用途。

【讨论】:

  • 完美!但是很令人困惑的是GenericTypeDefinition 不是一个透明和公开的类型。希望是这样,这样可能更合乎逻辑。抱歉代码不可读,我故意在两者之间设置断点。 PS:请允许我用我的upvote 取消downvoter 的影响:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-15
  • 2012-12-30
相关资源
最近更新 更多