Woes With External Assemblies

In all my years developing with .NET I haven't ever had a situation that I needed to load an external library into my code at runtime. Normally, I simply add the reference to my project and I'm on my way. As it turns out it can be slightly tricky if you're new to it.

I'm working on a project right now that is broken up into several pieces, one of which is a shared library that other external libraries use, specifically to inherit from a base class used by yet another part of the application. The shared class is actually referenced by each of the projects but one part of the application loads the assemblies in at runtime.

To summarize...

  1. SharedLibrary is referenced by MainApplication
  2. SharedLibrary is referenced by ExternalLibrary
  3. ExternalLibrary is loaded dynamically by MainApplication
  4. ExternalLibrary has classes that inherit from classes in SharedLibrary
  5. MainApplication needs to cast classes in ExternalLibrary into base class types found in SharedLibrary

So to illustrate what happens imagine two classes the abstract class ParentClass and the inheriting class ChildClass. This code made sense to me when I first typed it in but the results surprised me.

//create the instance
string path = @"d:\projects\libraries\exampleLibrary.dll";
object child = AppDomain.CurrentDomain
    .CreateInstanceFromAndUnwrap(path, "ExampleLibrary.ChildClass");
Type type = child.GetType();

//the details about the class
Console.WriteLine("Class is: {0}", child);
Console.WriteLine("Type Name: {0}", type.FullName);
Console.WriteLine("Load : {0}", type.Assembly.FullName);
Console.WriteLine("Ref  : {0}", typeof(ParentClass).Assembly.FullName);
Console.WriteLine("Is ChildClass  : {0}", child is ChildClass);
Console.WriteLine("Is ParentClass : {0}", child is ChildClass);
Console.WriteLine("Casting : {0}", child as ParentClass);

//the results
//Class is: ExampleLibrary.ChildClass
//Type Name: ExampleLibrary.ChildClass
//Load : ExampleLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
//Ref  : ExampleLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
//Is ChildClass  : False
//Is ParentClass : False
//Casting : <-- which is null

What the heck? Admittedly, I don't know why this fails - If I had to guess it would have something to do... well... we will discuss it in a moment...

I ended up with the following solution which worked exactly the way I had hoped.

//load the assembly manually
string path = @"d:\projects\libraries\exampleLibrary.dll";
byte[] bytes = File.ReadAllBytes(path);
AppDomain.CurrentDomain.Load(bytes);

//create the instance
object child = AppDomain.CurrentDomain
    .CreateInstanceAndUnwrap("ExampleLibrary", "ExampleLibrary.ChildClass");
Type type = child.GetType();

//the details about the class
Console.WriteLine("Class is: {0}", child);
Console.WriteLine("Type Name: {0}", type.FullName);
Console.WriteLine("Load : {0}", type.Assembly.FullName);
Console.WriteLine("Ref  : {0}", typeof(ParentClass).Assembly.FullName);
Console.WriteLine("Is ChildClass  : {0}", child is ChildClass);
Console.WriteLine("Is ParentClass : {0}", child is ChildClass);
Console.WriteLine("Casting : {0}", child as ParentClass);

//the results
//Class is: ExampleLibrary.ChildClass
//Type Name: ExampleLibrary.ChildClass
//Load : ExampleLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
//Ref  : ExampleLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
//Is ChildClass  : True
//Is ParentClass : True
//Casting : ExampleLibrary.ChildClass

Hmm... Not what I was expecting...

Even still, the code might be working - but one thing I always tell other developers is "that's great your code is working - now, why is it working?"

After poking around for a bit I noticed something that really should have been obvious from the beginning. Take a look at this code and you'll see where I'm headed...

Console.WriteLine("Load : {0}", type.Assembly.Location);
Console.WriteLine("Ref  : {0}", typeof(ParentClass).Assembly.Location);

//Load : d:\projects\libraries\exampleLibrary.dll
//Ref  : d:\projects\application\bin\debug\exampleLibrary.dll

Oh... so maybe not exactly the same type after all...

I suspect that by loading the bytes in directly took the whole 'location' thing out of picture. Of course, I need to read up on this some more before I'm positive but if any of you genius developers out there can confirm my theory then it would be greatly appreciated.

March 3, 2010

Woes With External Assemblies

Post titled "Woes With External Assemblies"