The following experimental codes/projects are using netcore 2.0 and netstandard 2.0 in VS2017. Let's say I have two versions of a third party dll v1.0.0.0 and v2.0.0.0, which contains only one class Constants.cs
.
//ThirdPartyDependency.dll v1.0.0.0
public class Constants
{
public static readonly string TestValue = "test value v1.0.0.0";
}
//ThirdPartyDependency.dll v2.0.0.0
public class Constants
{
public static readonly string TestValue = "test value v2.0.0.0";
}
Then I created my own solution named AssemblyLoadTest, which contains:
Wrapper.Abstraction: class library with no project references
namespace Wrapper.Abstraction
{
public interface IValueLoader
{
string GetValue();
}
public class ValueLoaderFactory
{
public static IValueLoader Create(string wrapperAssemblyPath)
{
var assembly = Assembly.LoadFrom(wrapperAssemblyPath);
return (IValueLoader)assembly.CreateInstance("Wrapper.Implementation.ValueLoader");
}
}
}
Wrapper.V1: class library with project reference Wrapper.Abstractions and dll reference ThirdPartyDependency v1.0.0.0
namespace Wrapper.Implementation
{
public class ValueLoader : IValueLoader
{
public string GetValue()
{
return Constants.TestValue;
}
}
}
Wrapper.V2: class library with project reference Wrapper.Abstractions and dll reference ThirdPartyDependency v2.0.0.0
namespace Wrapper.Implementation
{
public class ValueLoader : IValueLoader
{
public string GetValue()
{
return Constants.TestValue;
}
}
}
AssemblyLoadTest: console application with project reference Wrapper.Abstraction
class Program
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
{
Console.WriteLine($"AssemblyResolve: {e.Name}");
if (e.Name.StartsWith("ThirdPartyDependency, Version=1.0.0.0"))
{
return Assembly.LoadFrom(@"v1\ThirdPartyDependency.dll");
}
else if (e.Name.StartsWith("ThirdPartyDependency, Version=2.0.0.0"))
{
//return Assembly.LoadFrom(@"v2\ThirdPartyDependency.dll");//FlagA
return Assembly.LoadFile(@"C:\FULL-PATH-TO\v2\ThirdPartyDependency.dll");//FlagB
}
throw new Exception();
};
var v1 = ValueLoaderFactory.Create(@"v1\Wrapper.V1.dll");
var v2 = ValueLoaderFactory.Create(@"v2\Wrapper.V2.dll");
Console.WriteLine(v1.GetValue());
Console.WriteLine(v2.GetValue());
Console.Read();
}
}
STEPS
Build AssemblyLoadTest in DEBUG
Build Wrapper.V1 project in DEBUG, copy files in Wrapper.V1\bin\Debug\netstandard2.0\ to AssemblyLoadTest\bin\Debug\netcoreapp2.0\v1\
Build Wrapper.V2 project in DEBUG, copy files in Wrapper.V2\bin\Debug\netstandard2.0\ to AssemblyLoadTest\bin\Debug\netcoreapp2.0\v2\
Replace FULL-PATH-TO in AssemblyLoadTest.Program.Main with the correct absolute v2 path that you copied in step 3
Run AssemblyLoadTest - Test1
Comment FlagB line and uncomment FlagA line, run AssemblyLoadTest - Test2
Comment AppDomain.CurrentDomain.AssemblyResolve, run AssemblyLoadTest - Test3
My results and questions:
Test1 succeeds and prints v1.0.0.0 and v2.0.0.0 as expected
Test2 throws exception at
v2.GetValue()
System.IO.FileLoadException: 'Could not load file or assembly 'ThirdPartyDependency, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null'. Could not find or load a specific file. (Exception from HRESULT: 0x80131621)'
Question1: Why LoadFile with absolute path works as expected, while LoadFrom with relative path not working, meanwhile LoadFrom with relative path works for v1.0.0.0 in the first if
statement?
- Test3 fails with the same exception above at the same place, here my understanding is CLR locates the assemblies with the following priority rule:
Rule1: Check if AppDomain.AssemblyResolve is registered (highest priority)
Rule2: Otherwise check if the assembly is loaded.
Rule3: Otherwise search the assembly in folders(can be configured in probing and codeBase.
Here in Test3 where AssemblyResolve is not registered, v1.GetValue
works because Rule1 and Rule2 is N/A, AssemblyLoadTest\bin\Debug\netcoreapp2.1\v1
is in Rule3 scan candidates. When executing v2.GetValue
, Rule1 is still N/A, however Rule2 is applied here (if Rule3 is applied, why exceptions?)
Question2: Why the version is ignored even Wrapper.V2 reference ThirdPartyDependency.dll using
<Reference Include="ThirdPartyDependency, Version=2.0.0.0">
<HintPath>..\lib\ThirdPartyDependency\2.0.0.0\ThirdPartyDependency.dll</HintPath>
</Reference>