A first hand look from the .NET engineering teams
In the introductory post on CLR Binder (‘Understanding the Binder – Part 1’), we listed the set of steps that the CLR Binder follows, in order to locate an assembly and bind to it. On reading this, an obvious question comes to mind. What happens when all of these steps fail to locate the assembly? Does the binder simply quit looking?
It eventually does, but not before firing the AssemblyResolve event. The user can register an event handler for the AssemblyResolve event and then load the assembly that was intended to be loaded in the first place (or execute some other code appropriately).
The AssemblyResolve event itself has been around for a while now. So what’s changed in CLR 4? Prior to CLR 4, if an assembly A has a reference to another assembly B, and an AssemblyResolve event occurs for the referenced assembly (in this case, B), there is no means to know the identity of the parent assembly (or the referencing assembly, or A).
Why is this problematic? Let’s take this example. Let’s assume that an assembly FirstParent.dll references Child.dll. Let’s also assume SecondParent.dll also references Child.dll. On failing to load Child.dll, AssemblyResolve event is fired.
Now, while loading FirstParent.dll and SecondParent.dll using LoadFile(), an AssemblyResolve event is fired for Child.dll. Looking at the AssemblyResolve event, it is unclear as to which parent assembly actually triggered loading Child.dll. This is not helpful if the user wants to execute different code as a part of the ResolveEventHandler, depending on the parent assembly that caused attempting to load Child.dll.
In CLR 4, the AssemblyResolve event provides richer information that includes the parent assembly’s identity. System.ResolveEventArgs now has a property called RequestingAssembly, which is the assembly which requested the (unsuccessful) load of the child assembly, triggering an AssemblyResolve event.
With this new property, when AssemblyResolve event is fired, apart from the current assembly (which could not be located by the Binder), the parent assembly is also provided.
Thus, using this property,
(a) The user now knows which assembly caused the load event
(b) The event handler can now make use of the parent assembly that is passed.
You can read more about this new property here.
Can you clarify the user scenario that prompted this addition? It seems to me that any application that tries to load a different version of an assembly depending on which parent triggers the load is making itself dependent on implementation details of the CLR, the BCL, and the application itself. It would almost certainly make the application extremely fragile. Why would anyone want to do this?
What about adding this to the AssemblyLoad event as well? I could see that as being very useful for simple performance analysis. "The plugin from X.dll caused Y.dll to be loaded." Even better if we can add a method to get more detailed information (e.g. a stack trace that pointed to which block of code instigated the assembly loading).