Binding Context and LoadFrom

Suzanne has a discussion about binding context here.

A binding context is really just a loaded assembly cache. MSDN describes how runtime locates assemblies here. What is missing is that there is a step 5. When we find an assembly, we put it in a binding context, indexed by its full name (again, “name, Version=xxxx, Culture=xxxx, PublicKeyToken=xxxx”). Next time if you want to load the exact same assembly, we will hit it directly in the binding context. Bingo! Great perf gain.

(A little variance here is for non-strongly named assemblies, version is ignored. So if you bind to non-strongly named assembly use one version, and later you try to bind to the same assembly but with a different version number, you will get the first (cached) one, even the versions are different. Of course for strongly named assemblies you will get a different copy).

One side effect (but rather deliberately) is single instancing based on assembly full name. Once an assembly is loaded, you will always get the same copy later, given the same assembly name, no matter how many times you call Assembly.Load, or whatever settings you have changed.

This side effect is probably easy to understand for Assembly.Load, where assembly name is used. But it is not obvious for Assembly.LoadFrom (rather, it is surprising).

Assembly.LoadFrom and all its children(meaning, Assembly.Load initiated by the assembly returned by Assembly.LoadFrom) are put in a separate binding context called LoadFrom context. I'll talk about the difference between LoadFrom context and default load context in a separate blog.

Now imagine you have the following scenario:

You have an assembly called asm.dll. You rename it to asm1.dll and asm2.dll. Now you call Assembly.LoadFrom(”asm.dll”), then Assembly.LoadFrom(”asm1.dll”), then Assembly.LoadFrom(”asm2.dll”).

You know what will be returned in Assembly.LoadFrom(”asm1.dll”) and Assembly.LoadFrom(”asm2.dll”)?

asm.dll. Surprise!

The reason is caching in binding context is based on assembly full name. Since all of them have the same assembly full name, when Assembly.LoadFrom(“asm1.dll“) tries to put the result in LoadFrom context, it sees there is already a cache with the same name, and it throws away the one it found by probing, and returns the one in LoadFrom context, which is asm.dll.

Moral of the story? Assembly name matters. File path does not.