MSDN documents how the runtime locates assemblies here. The probing logic is implemented in fusion.

The documentation is correct if the given assembly name is fully specified. A fully specified assembly name is of the following format: “name, Version=xxxx, Culture=xxxx, PublicKeyToken=xxxx”. Now what if the given assembly name is not fully specified? The probing logic is different from what MSDN described.

Assume Assembly.Load(”MyAsm, Culture=Neutral”) is called. Here is exactly what happened, listed in the exact same steps MSDN described.

1. Binding policy is not applied.
2. We do not check if the assembly has been loaded before or not.
3. We do not probe GAC.
4. We probe the appbase the same way MSDN describes in step 4.
5. If we do not find a matching assembly in appbase, we return a load failure.
6. If we do find a matching assembly in appbase, we do the following extra work. (matching here means the file name of the assembly is the simple name of the assembly plus “.dll” or “.exe”). Assuming we find MyAsm.dll in appbase.
    6.1 Check if the assembly found matches all the fields specified in Assembly.Load. In our example, we will check if MyAsm.dll's culture is neutral. If any of the fields does not match, we will return FUSION_E_REF_DEF_MISMATCH. If all match, we go to next step.
    6.2 We take the assembly name of the assembly found(Which is fully specified), and redo step 1,2 and 3. This means, we will apply binding policy on the name, then check if the assembly has been loaded or not, and probe GAC if the assembly has not been loaded before.
    6.3 If something is returned in step 6.2, we return the result of 6.2 to CLR loader.
    6.4 If we do not find anything in 6.2, if there was no binding policy redirect, we return result of 6 to CLR loader. If there was binding policy redirect, we throw away the result of 6, and go back to 4, and keep probing appbase. This time, we terminate the probing at 4(and will not repeat step 6.1-6.4).

This sounds rather complicated. But the important idea is, we use the original partial name to find an assembly in appbase. We then use the assembly name of that assembly to do a real bind.

Some scenarios:
1. No assembly in appbase. You get an assembly load failure.
2. Assembly in appbase, but does not match all fields. You get a load failure.
3. Assembly in appbase, and matches all fields.
    3.1 No binding policy. You get the assembly in appbase.
    3.2 There is binding policy.
        3.2.1 Post policy assembly find in GAC. You get the assembly in GAC. 
        3.2.2 Assembly does not exist in GAC, but there is a codebase hint in policy. You get the assembly in the codebase hint.
        3.2.3 Assembly not in GAC, and there is no codebase hint. You get a load failure.

MSDN also documents behavior of Assembly.LoadWithPartialName here. Basically what Assembly.LoadWithPartialName does is first it does an Assembly.Load() with the partial name. If Assembly.Load fails, it then enumerates GAC to find all the qualified assemblies in GAC, and returns the one with the highest version.

Of course, Suzanne recommends against using Assembly.LoadWithPartialName here.

All the decisions above are recorded in fusion binding log. You can find instruction how to use fusion binding log viewer here in MSDN. I probably will have a blog entry on fusion binding log later.