I have encountered CLR loader problem last week. Actually, it was not CLR loader problem, but my problem.
The problem is first shown up from a question on the Japanese .NET discussion forum. I partially answered the question with only
load context in mind. Then someone
complemented my answer with loadfrom context. At first, I didn't think loadfrom context would solve the problem, and gave it a quick try. This quick try made me confused and bothered Suzanne Cook on Sunday evening.
My code is like this:
-- base2.cs
public class Base2 {}
-- derived2.cs
public class Derived2 : Base2 {}
-- app2.cs
using System;
using System.Reflection;
class App {
static void Main() {
Assembly bas = Assembly.LoadFrom("c:\\temp\\repro\\subdir\\base2.dll");
Assembly der = Assembly.LoadFrom("c:\\temp\\repro\\derived2.dll");
Type t;
try {
t = der.GetTypes()[0];
} catch (ReflectionTypeLoadException ex) {
foreach (Exception e in ex.LoaderExceptions)
Console.WriteLine(e.Message + e.GetType().ToString());
return;
}
object o = Activator.CreateInstance(t);
}
}
--makefile
base2.dll : base2.cs
csc /t:library /out:subdir\base2.dll base2.cs
derived2.dll : derived2.cs base2.dll
csc /t:library /r:subdir\base2.dll derived2.cs
app2.exe : app2.cs
csc app2.cs
all : base2.dll derived2.dll app2.exe
... Nmake creates three assemblies. derived2.dll and app2.exe is in the same folder, while base2.dll is in the sub folder of the folder other two are placed. If I run app2.exe, the code in the try block throws ReflectionTypeLoadException, which contains FileNotFoundException for the base2 assmbly.
I thought this was because the metadata to solve inheritance hierarchy (in this case it is the class Base2) should always be from the load context, and calling LoadFrom for base2.dll would not help solving it.
I then port my app2.cs to test.aspx like below, because the original question on the discussion forum is about ASP.NET:
--test.aspx
<%@Page language="C#"%>
<%@Import Namespace="System.Reflection"%>
<%
Assembly bas = Assembly.LoadFrom("c:\\temp\\repro\\subdir\\base2.dll");
Assembly der = Assembly.LoadFrom("c:\\temp\\repro\\derived2.dll");
Type t;
try {
t = der.GetTypes()[0];
} catch (ReflectionTypeLoadException ex) {
foreach (Exception e in ex.LoaderExceptions)
Response.Write(e.Message + e.GetType().ToString());
return;
}
object o = Activator.CreateInstance(t);
%>
... and it worked without throwing an exception. I was confused. I was so confused I made up my mind and asked Suzanne for help.
At first she couldn't reproduce my problem, even I sent her the complete source code. It was Friday, and I almost gave up this problem to solve completely, tried to forget the problem throughout the weekend. I sent her my thanks on Monday morning. Well, it was Monday morning for the +0900 time zone, but for her it was Sunday evening.
What surprised me was that she responded my thanks immediately, asking me to send her the assemblies I was using. I sent them to her, and she found what a stupid question she was bothered in the lovely Sunday evening. All the problems that confused me has already been answered by Suzanne on her blog, that I am one of the loyal reader. But that entry just slipped my mind. When I placed my app2.exe to where it can't load derived2.dll in the load context, it worked perfectly. In other words, to have CLR loader and resolver find dependent assemblies ( by using loadfrom context ), they (dependent assemblies) must be placed where CLR loader can't find them ( in the load context ).
I have learned two things from this lesson; a) CLR loader have more secrets than I will ever know, and b) Suzanne Cook knows her staff. She is terrific. Thanks! and forgive me disturbing your Sunday evening...