Viewing types with Reflection-Only
It's natural for a tool to use Reflection-Only loading to load an assembly and view the types in it. For example, I used this in my pdb2xml tool. However, I missed an important detail that I wanted to warn you about after getting it wrong myself.
Consider the following snippet which will print all the type names in an assembly.
// This has a bug!!!! See correct version below
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
class Program
{
static void Main(string[] args)
{
string filename = args[0];
Assembly a = System.Reflection.Assembly.ReflectionOnlyLoadFrom(filename);
Console.WriteLine("Opened assembly:{0}", filename);
foreach (Type t in a.GetTypes())
{
Console.WriteLine(" " + t.FullName);
}
}
}
So if you compile it as type_sniff.exe and run it on itself, it would print:
>type_sniff.exe type_sniff.exe
Opened assembly:type_sniff.exe
Program
Looks about right. But there's a problem. Pop quiz: what's wrong? (and I'm not talking about additional error checking, etc).
...
...
Answer:
Run it on some other inputs and you'll get a ReflectionTypeLoadException. You just need a type that derives from a type in another dll. For example:
// defined in a.dll
public class Foo
{
public Foo()
{
}
}
// defined in b.dll, compiled as /r:a.dll
public class Bar : Foo
{
Bar()
{
}
}
And then run: type_sniff.exe b.dll
C:\bug\type_sniff\type_sniff\bin\Debug>type_sniff.exe b.dll
Opened assembly:b.dll
Unhandled Exception: System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
at System.Reflection.Module.GetTypesInternal(StackCrawlMark& stackMark)
at System.Reflection.Assembly.GetTypes()
at Program.Main(String[] args)
Following the exception message (and Suzanne's advice) to look at the LoaderException property, I see (emphasis mine):
{"Cannot resolve dependency to assembly 'a, Version=2.1.0.0, Culture=neutral, PublicKeyToken=ebb8d478f63174c0' because it has not been preloaded. When using the ReflectionOnly APIs, dependent assemblies must be pre-loaded or loaded on demand through the ReflectionOnlyAssemblyResolve event.":"a, Version=2.1.0.0, Culture=neutral, PublicKeyToken=ebb8d478f63174c0"}
So what happened was that it tried to get the System.Type for Bar, but to resolve the type it needs to load the base class, which is in another dll. Reflection-Only context doesn't do binding policy so it can't find that dll. The LoaderException hint says to use the ReflectionOnlyAssemblyResolve, which provides more information about this.
So I add a ReflectionOnlyAssemblyResolve event. Intellisense was very helpful in generating the glue code.
So now the code looks like (key additions highlighted in yellow):
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
class Program
{
static void Main(string[] args)
{
string filename = args[0];
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
Assembly a = System.Reflection.Assembly.ReflectionOnlyLoadFrom(filename);
Console.WriteLine("Opened assembly:{0}", filename);
foreach (Type t in a.GetTypes())
{
Console.WriteLine(" " + t.FullName);
}
}
static Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
return System.Reflection.Assembly.ReflectionOnlyLoad(args.Name);
}
}
This assumes that all the dependencies are in the working directory. You could have a fancier AssemblyResolve event that used the current directory from the original filename and had fancier search logic.
And when I run it on b.dll, it works properly:
C:\bug\type_sniff\type_sniff\bin\Debug>type_sniff.exe b.dll
Opened assembly:b.dll
Bar
Concluding thoughts:
Some cool things: There were some good things that made this much easier to diagnose:
- The exception had useful information, specifically a useful explanation and unique keywords that I could search MSDN for more details.
- The intellisense was very helpful in generating the glue code. That made it much faster to react.
Room for improvement: This falls under the category of evil bug that works most of the time; but fails on certain inputs. However, it seems like this ought to be avoidable (better library design) or detectable (fxcop rule). Basically any use of GetTypes() on reflection-only assemblies without handling the ReflectionOnlyAssemblyResolve event could be broken.