列出运行时从开放式泛型类型创建的闭合类型列表。

10
当我列出当前AppDomain中的所有类型时,我可以看到我的泛型类型与泛型占位符。然而,如果我使用类型实例化我的泛型类型,然后列出appDomain中的所有类型,我将不会看到新创建的闭合类型。
在下面的示例中,输出仅为:
Foo`1[T]

我正在寻找封闭式的类型:

Foo`1[System.Int32]

有没有办法查看运行时基于我的开放泛型类型创建的已关闭类型?
class Foo<T>
{
}

class Program
{
    static void Main(string[] args)
    {
        var tmp = new Foo<int>();
        ListTypes();
    }

    private static void ListTypes()
    {
        var types = from assembly in AppDomain.CurrentDomain.GetAssemblies()
                        from type in assembly.GetTypes()
                        where type.Name.Contains("Foo")
                        select type;

        foreach (var type in types)
            Console.WriteLine(type.ToString());
    }
}

我也尝试通过泛型参数查找所有类型,希望能发现闭合类型。

class Foo<T>
{
}

class Bar
{
}

class Program
{
    static void Main(string[] args)
    {
        var tmp = new Foo<Bar>();
        ListTypes();
    }

    private static void ListTypes()
    {
        var types = from assembly in AppDomain.CurrentDomain.GetAssemblies()
                        from type in assembly.GetTypes()
                        where type.IsGenericType
                        && type.GetGenericArguments().Contains(typeof(Bar))
                        select type;

        foreach (var type in types)
            Console.WriteLine(type.ToString());
    }
}

这只是为了满足我的好奇心。


如果我理解正确的话,这个反射会简单地获取在元数据中定义的类型,而在你的情况下只包含通用类型定义。由于特定类型可以在运行时从通用类型动态构造(再次使用反射,传入通用参数)- 你可以看到无法将它们放入元数据中...所以,其他机制(而非元数据探索)必须被用来找到已创建的特定类型。 - M.A. Hanin
我明白了。这就解释了为什么我无法看到在运行时创建的类型,因为它们不在反射查询的元数据中。我想知道那个其他机制是什么? - Nick VanderPyle
1
mscorlib 中有一个名为 TypeNameParser 的私有类型,它有一个 GetNames 方法,返回一个字符串数组。但是当我尝试在反射下使用它时,我得到了致命错误,提醒我我对 COM 对象和互操作知之甚少,通常不应该搞乱 mscorlib 中的私有类型 :-P 尽管如此,我还在寻找一种优雅的解决方案。 - M.A. Hanin
2个回答

5
据我所理解,在这种情况下,Foo<T> 是一个开放的未绑定泛型类型,因此在运行时,CLR 将使用它作为蓝图/骨架来构建和关闭具有指定类型参数类型(Foo<int>Foo<object> 等)的泛型类型。因此,Foo<int> 基本上是 Foo<T> 骨架的运行时构造实现。

现在,在运行时,您可以通过使用 typeof(Foo<int>)typeof(Foo<>).MakeGenericType(new[] { typeof(int) }) 来获取 Foo<int> 的类型,并且它们不是相同的 Type,也没有意义。但是仔细观察,您会发现 typeof(Foo<T>)typeof(Foo<int>) 共享相同的元数据标记和 GUID。

另一个有趣的事情是,typeof(Foo<int>).Assembly 将是您期望的内容,但正如您已经注意到的那样,您无法从程序集中获取该类型。

这是因为 Foo<int> 没有在程序集中定义(您可以使用 Reflector/ILSpy 检查程序集元数据)。在运行时,CLR 将为 Foo<int> 创建(“构造”)Foo<T> 的专门化(“封闭”)版本(因此-未绑定的开放泛型类型定义的构造封闭类型),并“赋予”它一个 Type。因此,除非 CLR 直接公开其在运行时生成的封闭泛型类型列表,否则您就没有办法。

此外,这里是一段可能确认我所说的话的代码片段:

尽管每个泛型类型的构造,例如 Node< Form > 和 Node< String >,都具有自己独特的类型标识符,但 CLR 能够在类型实例之间重用大部分实际 JIT 编译的代码。这极大地减少了代码膨胀,并且是可能的,因为泛型类型的各种实例在运行时扩展。编译时,构造类型的全部内容只是一个类型引用。当 A 和 B 两个程序集都引用第三个程序集中定义的泛型类型时,它们的构造类型在运行时扩展。这意味着,除了共享 CLR 类型标识符(如果适用)之外,来自 A 和 B 程序集的类型实例化还共享运行时资源,例如本机代码和扩展的元数据。

http://msdn.microsoft.com/en-us/magazine/cc163683.aspx


2
这是一些复杂的“运行时CLR魔法”:对于每个“加载”的封闭类型,都会调用Foo<T>的静态构造函数 :-) P.S. 当你需要他的时候,Skeet在哪里? - M.A. Hanin

1
Ivan的回答基本正确,但声称汇编元数据不包含有关构造类型的任何信息并不完全正确。所有构造类型都在使用它们的程序集中定义,像Mono.Cecil这样的工具可以让您看到它们。构造类型不通过反射公开,即使是Mono.Cecil也很难找到它们所有的位置。
基本上,您必须遍历程序集中使用的所有类型,例如属性类型、返回类型、局部变量类型等。这些信息包含在程序集元数据中,并且使用Mono.Cecil可以相对容易地枚举它们。然后只需应用简单的过滤器来检测类型是否被构造。请注意,您可能需要遍历引用泛型类型定义的多个程序集,以查找从中构造的所有类型。
这种解决方案有两个限制。首先,通过反射构造的类型自然不会出现在任何程序集中。其次,一些构造类型嵌入在泛型类型/方法中,它们的泛型类型参数仅在使用特定泛型类型参数实例化其父类型/方法之后才会知道。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接