Search code examples
c#.net-assemblygettype

C# Assembly.GetType fails for AssemblyQualifiedName but works for FullName


Got a strange issue here. We're building a scripting system for our platform using Roslyn and reflection. In order to keep things as concise as possible, this works well for most types but fails for others (I have not been able to see a pattern here).

One of our requirements is that we reference any possible .net assembly so in order to create object instances we have to use the fully qualified assembly names. Normally I use the following helper method to first get the object Type:

This works well enough in most cases, however let's look at the following qualified type string: "Serilog.LoggerConfiguration, Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10" (let's call this typeString).

public static Type GetType(string typeName)
{
    var type = Type.GetType(typeName);
    if (type != null) return type;
    foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
    {
        type = a.GetType(typeName);
        if (type != null)
            return type;
    }
    return null;
}

AppDomain.CurrentDomain.GetAssemblies() contains the assembly mentioned above. Using the VS immediate window I can take that particular assembly and try the following (let's call the Serilog assembly asm):

  1. asm.GetType(typeString) -> returns null. Strange since the assembly has the type in it. I checked by calling asm.GetTypes().
  2. asm.GetType(typeString, false, false) -> returns null. Worth a shot.
  3. asm.GetType("Serilog.LoggerConfiguration") -> returns the actual Type!

In short, I can find the type by using the FullName but not by using the AssemblyQualifiedName? This doesn't seem to make much sense. Armed with this information, I try some further tests (attempt to locate the Type in the Assembly using text matching):

asm.GetTypes().Where(x=>x.AssemblyQualifiedName == "Serilog.LoggerConfiguration, Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10") -> this returns the correct Type.

My two questions regarding this are:

  1. Why does my helper method fail to locate the type using the fully qualified name? By all accounts it should work.
  2. Failing to find the type with my helper method, is it safe to add the above Linq search in case every other thing fails? I'm thinking reliability and performance, but any comments are welcome.

EDIT 1 In order to provide a working repro/example, I've done the following:

  1. Create a new VS Solution, console app, .NET Framework 4.6.2
  2. Add Nuget Package Serilog, version 2.12.0
  3. Build the solution (so that the serilog.dll file will be placed within the bin folder)
  4. Create a new folder within the bin folder, named "Lib" and move Serilog.dll there (to prevent it from being deleted on the next step)
  5. Remove the Serilog nuget reference from the solution
  6. Execute the following code under Main.cs:
static void Main(string[] args)
{
   //this is the fully qualified assembly name
   string aqn = "Serilog.LoggerConfiguration, Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10";

   //load assembly from file and insert it into the app domain
   Assembly asm = Assembly.LoadFrom("Lib\\Serilog.dll");
   AppDomain.CurrentDomain.Load(asm.GetName());

   //this is null - fails only if the assembly is loaded using the Assembly.LoadFrom method.
   //Otherwise, if it's added to the solution as a nuget package it works.
   var direct = Type.GetType(aqn);

   //using Linq I can correctly find the Type.
   var linq = AppDomain.CurrentDomain.GetAssemblies().Where(x => x.FullName.Contains("Serilog")).FirstOrDefault()
          .GetTypes().Where(y=>y.AssemblyQualifiedName == aqn).FirstOrDefault();
}

Solution

  • Finally figured it out. After some digging around it seems to be linked to the load context and needs a custom assembly resolver (more on this on ReflectionTypeLoadException from Roslyn-generated assembly)

    In short, by implementing this custom assembly resolver, Type.GetType(AssemblyQualifiedName) works correctly, even for assemblies loaded using Assembly.LoadFrom(). This even works for nested types (defined using qualified assembly names).

    One note: Be sure to call the Init() method only once, as otherwise you'll be reseting the assemblies dictionary.