I have two assemblies: HelloWorld.exe and Hello.dll. The exe is the main assembly and the dll is being used by the main assembly.
I compiled both HelloWorld.exe (1.0.0) and Hello.dll (1.0.0). I placed the assemblies on a different folder.
I then changed the version of Hello.dll to 2.0.0 and proceeded to overwrite the Hello.dll 1.0.0 with the 2.0.0 version. I then launch HelloWorld.exe and it worked fine.
I expected it to crash and burn immediately because the referenced Hello.dll when I compiled the EXE was 1.0.0. Now, the 1.0.0 DLL has been replaced by 2.0.0 but it still worked!
As per MSDN:
By default, an assembly will only use types from the exact same assembly (name and version number) that it was built and tested with. That is, if you have an assembly that uses a type from version 1.0.0.2 of another assembly, it will (by default) not use the same type from version 1.0.0.4 of the other assembly. This use of both name and version to identify referenced assemblies helps avoid the "DLL Hell" problem of upgrades to one application breaking other applications.
Questions:
- Why did it work?
- How to make it NOT work?
- BONUS QUESTION: What happens during the build process? Isn't the version of external dependencies hard coded to the main dependency?
Note that Hello.dll is not strongly named.
Here's the manifest for HelloWorld.exe:
// Metadata version: v2.0.50727
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 2:0:0:0
}
.assembly extern Hello
{
.ver 2:0:0:0
}
.assembly HelloWorld
{
...//snipped
}
Here's the Assembly Binding Log taken from Fuslogvw.exe (Assembly Binding Log Viewer):
=== Pre-bind state information ===
LOG: User = ian.uy
LOG: DisplayName = Hello, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null
(Fully-specified)
LOG: Appbase = file:///C:/Users/ian.uy/Desktop/HelloWorld/HelloWorld/bin/Debug/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = NULL
Calling开发者_高级运维 assembly : HelloWorld, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///C:/Users/ian.uy/Desktop/HelloWorld/HelloWorld/bin/Debug/Hello.DLL.
LOG: Assembly download was successful. Attempting setup of file: C:\Users\ian.uy\Desktop\HelloWorld\HelloWorld\bin\Debug\Hello.dll
LOG: Entering run-from-source setup phase.
LOG: Assembly Name is: Hello, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
LOG: Binding succeeds. Returns assembly from C:\Users\ian.uy\Desktop\HelloWorld\HelloWorld\bin\Debug\Hello.dll.
LOG: Assembly is loaded in default load context.
I've faced the same thing and tried to look into the msbuild logs:
msbuild /v:detailed /t:build
The following lines looked interesting:
Unified Dependency "Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed". Using this version instead of original version "7.0.0.0" in "C:\src\BindingTest\Lib1\bin\Debug\Lib1.dll" because AutoUnify is 'true'.
Additionally, If you check out resulting app.config file after the build, you would probably see automatic binding redirect there that your app.config did not initially have.
So the behavior we observe has to do with "automatic assembly unification" and "automatic binding redirection" msbuild processes.
Here is what documentation says about AutoUnify
parameter:
When true, the resulting dependency graph is automatically treated as if there were anApp.Config file passed in to the AppConfigFile parameter. This virtual App.Config file has a bindingRedirect entry for each conflicting set of assemblies such that the highest version assembly is chosen. A consequence of this is that there will never be a warning about conflicting assemblies because every conflict will have been resolved.
When true, each distinct remapping will result in a high priority comment showing the old and new versions and that AutoUnify was true.
Finaly, if you want to observe the "failure" you can call msbuild with following arguments:
msbuild /v:d /t:build /p:AutoUnifyAssemblyReferences=false;AutoGenerateBindingRedirects=false
Why did it work?
Because you have specified so ;)
How to make it NOT work?
- Rightclick on the DLL in Solution Explorer
- Select Properties
- Select Use specific version
BONUS QUESTION: What happens during the build process? Isn't the version of external dependencies hard coded to the main dependency?
Not unless you specify so. It's off per default. Well designed assemblies should be backwards compatible and the version shouldn't really matter.
The runtime distinguishes between regular and strong-named assemblies for the purposes of versioning. Version checking only occurs with strong-named assemblies.
(source: https://learn.microsoft.com/en-us/dotnet/framework/app-domains/assembly-versioning)
Therefore, the answers are as follows:
Why did it work?
Because the assembly isn't strong-named
How to make it NOT work?
Make the assembly strong-named
What happens during the build process?
Depends
Use specific version
= false: compiles against the first file matching the reference in the project, takes any versionUse specific version
= true: compiles against the first file found matching the reference including the specified version in the project
Isn't the version of external dependencies hard coded to the main dependency?
Yes, the versions of referenced assemblies are hard-coded. You can verify this by using a decompiler (e.g. ILSpy) to read this information
精彩评论