The question originates from the usage of the NuGet package Microsoft.CodeAnalysis 2.0.0 in a .NET Framework 4.6.2 project.
The Microsoft.CodeAnalysis.dll
has a reference to: System.Threading.Tasks, Version=4.0.10.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
However, this assembly is nowhere to be found.
The System.Threading.Tasks
NuGet package contains no assemblies for .NET 4.6.2 and the GAC contains only version 4.0.0.0 of this assembly (which in turn contains only type forwarders).
But despite this everything works as it should during runtime, and indeed the assembly actually loaded is System.Threading.Tasks, Version=4.0.0.0
.
The app.config
of my project contains no binding redirects for this assembly either. So how does the CLR resolve it to version 4.0.0.0 of System.Threading.Tasks
?
The problem occurs when attempting to load assemblies in the Reflection-Only context, where we have to provide the assembly resolve handler ourselves, since the requested assembly is nowhere to be found and there are no binding redirects or anything hinting that we should actually load a lower version than the requested assembly.
Update
It was suggested that this was a retargetable assembly. Looking at the post here, it suggests that the metadata should contain something like the following:
.assembly extern retargetable mscorlib
{
...
}
But when I look at Microsoft.CodeAnalysis.dll
in ILdasm I cannot find any mention of something being retargetable in it. The reference looks like this:
.assembly extern System.Threading.Tasks
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 4:0:10:0
}
Could it be that ILdasm is hiding something here?
If I try the following:
Assembly codeAnalysisAssembly =
Assembly.ReflectionOnlyLoadFrom("Microsoft.CodeAnalysis.dll");
foreach (var reference in codeAnalysisAssembly.GetReferencedAssemblies())
{
Console.WriteLine(reference.FullName);
}
None of the assembly names returned contains any information that it references a "retargetable" assembly (that I can find).
However the runtime resolves the assembly here does not seem to work in the Reflection-only context though:
// This call succeeds and prints: System.Threading.Tasks, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
Console.WriteLine(Assembly.Load("System.Threading.Tasks, Version=4.0.10.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a").FullName);
// This call throws an exception saying it cannot find the requested assembly.
Console.WriteLine(Assembly.ReflectionOnlyLoad("System.Threading.Tasks, Version=4.0.10.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a").FullName);
If fact, it seems that Assembly.Load()
will happily load the 4.0.0.0
version of the System
assembly as well, no matter which version we request as long as the major.minor part of the version is equal to 4.0
. Is there some publisher policy in effect here maybe?
Concrete Questions
- What mechanism is the CLR using to "ignore" the versions of the requested System-assemblies when using
Assembly.Load
? - Is it possible to implement a handler for
AppDomain.ReflectionOnlyAssemblyResolve
that would mimic this behavior when usingAssembly.ReflectionOnlyLoad()
? At least without resorting to the native Fusion-API? And if so, how should this be done?