Dynamic type resolution - multiple calls to Type.GetType or scanning all types in assembly?

443 Views Asked by At

I am currently implementing a type name resolution scheme. The available information is very similar to what you would get in a regular C# source project:

  • a list of Assembly references
  • a list of using namespaces

At runtime, for each type name to resolve, the name is either fully qualified (with namespace, but not with assembly name) or it is a simple name that is expected to come from one of the using namespaces.

In order to find the Type matching each identifier, I am considering one of two strategies:

  1. Preload all assemblies using Assembly.Load and scan all types. All simple names that have a namespace prefix matching one of the using namespaces will be precached. Also a dictionary is created mapping each namespace qualified type name in the assembly directly to its Type. Conflict resolution will be performed accordingly during the loading phase.

  2. Do not preload anything. Whenever a type name arrives, try the following sequence:

    • Assume the name is fully qualified; concatenate each of the referenced assembly names in turn to create an assembly-qualified name and call Type.GetType to see if we get a valid type.

    • If the above step does not produce any valid type and the name has no prefix, assume it is a simple name; repeat the above step but each time prepend the simple name with one of the using namespaces to see if we get a valid type.

Which approach would be preferable and what are the pros and cons?

It is unclear at the moment how many types will need to be resolved in this way for every run but I'm assuming anything between 10 and 100. Multiple assemblies can be referenced at any point, each with potentially hundreds of types.

I am interested in knowing the relative performances of both strategies and curious to hear about existing best practices to deal with this scenario. In addition to performance, it would be very helpful to understand any difference in behavior side-effects: for instance, does scanning all types in an Assembly execute all the static constructors for loaded types? I would prefer an approach that would be as lazy as possible in running any code from referenced assemblies.

1

There are 1 best solutions below

0
On BEST ANSWER

In the new .NET Standard it turns out there is a much better way to do it, by using the new System.Reflection.Metadata namespace. In their own words:

"This packages provides a low-level .NET (ECMA-335) metadata reader.

It's geared for performance and is the ideal choice for building higher-level libraries that intend to provide their own object model, such as compilers."

Here's a small code snippet for listing the name and namespace for all type definitions in a given assembly file:

using System;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;

namespace MetadataTest
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var stream = File.OpenRead(args[0]))
            using (var peFile = new PEReader(stream))
            {
                var metadataReader = peFile.GetMetadataReader();
                foreach (var type in metadataReader.TypeDefinitions)
                {
                    var definition = metadataReader.GetTypeDefinition(type);
                    var name = metadataReader.GetString(definition.Name);
                    var ns = metadataReader.GetString(definition.Namespace);
                    Console.WriteLine(ns + "." + name);
                }
            }
        }
    }
}