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.