List closed types that runtime has created from op

2020-06-12 03:51发布

When I list all the types in the current AppDomain, I see my generic types with the generic placeholders. However, if I instantiate my generic types with a type and then list all the types in the appDomain, I don't see the newly created closed types.

In the example below, the output is only:

Foo`1[T]

I'm looking for the closed type:

Foo`1[System.Int32]

Is there a way to see the closed types that the runtime has created for me based on my open generic types?

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

I have also tried finding all types by the generic argument in hopes of discovering the closed type.

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

This is just to satisfy my curiosity.

2条回答
兄弟一词,经得起流年.
2楼-- · 2020-06-12 04:24

As far as I can understand in this case Foo<T> is an open unbound generic type, so at runtime the CLR will use it as a blueprint/skeleton to construct and close a generic type with the type parameter type specified (Foo<int>, Foo<object>, etc.). So basically Foo<int> is a runtime constructed implementation of the Foo<T> skeleton.

Now, at run-time you can get the type of Foo<int> either by using typeof(Foo<int>) or typeof(Foo<>).MakeGenericType(new[] { typeof(int) }) and it's not the same Type and it wouldn't make sense for it to be. But look closer and you will see that both typeof(Foo<T>) and typeof(Foo<int>) share the same metadata token and GUID.

Another interesting thing is that typeof(Foo<int>).Assembly will be what you would expect, but as you've noticed already you can't get that type from the Assembly.

That's because Foo<int> is not defined in the assembly (you can check the assembly metadata with Reflector/ILSpy). At run-time the CLR will create ("construct") a specialized ("closed") version of the Foo<T> for Foo<int> (so - constructed closed type of an unbounded open generic type definition) and "give" it a Type. So unless the CLR exposes directly somehow the list of closed generic types it generates at run-time you are out of luck.

Also here is a snippet that might confirm what I am saying:

Even though each construction of a generic type, such as Node< Form > and Node< String >, has its own distinct type identity, the CLR is able to reuse much of the actual JIT-compiled code between the type instantiations. This drastically reduces code bloat and is possible because the various instantiations of a generic type are expanded at run time. All that exists of a constructed type at compile time is a type reference. When assemblies A and B both reference a generic type defined in a third assembly, their constructed types are expanded at run time. This means that, in addition to sharing CLR type-identities (when appropriate), type instantiations from assemblies A and B also share run-time resources such as native code and expanded metadata.

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

查看更多
Anthone
3楼-- · 2020-06-12 04:25

Ivan's answer is mostly right, but claiming that assembly metadata doesn't contain any information about constructed types is not quite correct. All constructed types are defined in assembly that uses them and tools like Mono.Cecil let you see it. Constructed types are not exposed through reflection and even Mono.Cecil makes it quite hard to locate them all.

Basically you have to walk through all types that are used in the assembly, e.g. types of properties, return types, local variable types, etc. This information is contained in assembly metadata and reasonably easy to enumerate with Mono.Cecil. Then just apply simple filter that detects whether the type is constructed. Note that you might have to walk through several assemblies that reference the generic type definition in order to find all types constructed from it.

There are two limitations to this solution. Firstly, types constructed via reflection naturally don't appear in any assembly. Secondly, some constructed types are embedded in generic types/methods and their generic type arguments are known only after their parent type/method is instantiated with particular generic type arguments.

查看更多
登录 后发表回答