.net visual - How best to use File Version and Assembly Version?

during set (6)

The KB article mentions the most important distinction: File versions are only used for display purposes, whereas the assembly version plays an important part in the .NET loading behaviour.

If you change the assembly version number, then the identity of your assembly as a whole has changed. Developers will need to rebuild to reference your new version (unless you put some auto-versioning "policy" in place) and at runtime only assemblies with matching version numbers will be loaded.

This is important in my environment, where we need an incrementing, highly visible version number for audit purposes, but we don't want to force developers to rebuild or have many versions concurrently in production. In this case for backwardly-compatible minor changes we update the file version, but not the assembly version.

In .NET there are two version numbers available when building a project, File Version and Assembly Version. How are you using these numbers? Keeping them the same? Auto-incrementing one, but manually changing the other?

Also what about the AssemblyInformationalVersion attribute?

I'd found this support Microsoft Knowledge Base (KB) article that provided some help: How to use Assembly Version and Assembly File Version.

In a scenario where I have multiple file assemblies (i.e. 1 exe and 5 dlls) I will use a different file version for each, but the same assembly version for all of them, allowing you to know which exe each of the dlls go with.

AssemblyVersion pretty much stays internal to .NET, while AssemblyFileVersion is what Windows sees. If you go to the properties of an assembly sitting in a directory and switch to the version tab, the AssemblyFileVersion is what you'll see up top. If you sort files by version, this is what's used by Explorer.

The AssemblyInformationalVersion maps to the "Product Version" and is meant to be purely "human-used".

AssemblyVersion is certainly the most important, but I wouldn't skip AssemblyFileVersion, either. If you don't provide AssemblyInformationalVersion, the compiler adds it for you by stripping off the "revision" piece of your version number and leaving the major.minor.build.

Here is a blog post I recently wrote that delves into the details of assembly versioning...

Versioning of assemblies in .NET can be a confusing prospect given that there are currently at least three ways to specify a version for your assembly.

Here are the three main version-related assembly attributes:

// Assembly mscorlib, Version
[assembly: AssemblyFileVersion("2.0.50727.3521")]
[assembly: AssemblyInformationalVersion("2.0.50727.3521")]
[assembly: AssemblyVersion("")]

By convention, the four parts of the version are referred to as the Major Version, Minor Version, Build, and Revision.

The AssemblyFileVersion is intended to uniquely identify a build of the individual assembly

Typically you’ll manually set the Major and Minor AssemblyFileVersion to reflect the version of the assembly, then increment the Build and/or Revision every time your build system compiles the assembly. The AssemblyFileVersion should allow you to uniquely identify a build of the assembly, so that you can use it as a starting point for debugging any problems.

On my current project we have the build server encode the changelist number from our source control repository into the Build and Revision parts of the AssemblyFileVersion. This allows us to map directly from an assembly to its source code, for any assembly generated by the build server (without having to use labels or branches in source control, or manually keeping any records of released versions).

This version number is stored in the Win32 version resource and can be seen when viewing the Windows Explorer property pages for the assembly.

The CLR does not care about nor examine the AssemblyFileVersion.

The AssemblyInformationalVersion is intended to represent the version of your entire product

The AssemblyInformationalVersion is intended to allow coherent versioning of the entire product, which may consist of many assemblies that are independently versioned, perhaps with differing versioning policies, and potentially developed by disparate teams.

“For example, version 2.0 of a product might contain several assemblies; one of these assemblies is marked as version 1.0 since it’s a new assembly that didn’t ship in version 1.0 of the same product. Typically, you set the major and minor parts of this version number to represent the public version of your product. Then you increment the build and revision parts each time you package a complete product with all its assemblies.” — Jeffrey Richter, CLR via C# (Second Edition) p. 57

The CLR does not care about nor examine the AssemblyInformationalVersion.

The AssemblyVersion is the only version the CLR cares about (but it cares about the entire AssemblyVersion)

The AssemblyVersion is used by the CLR to bind to strongly named assemblies. It is stored in the AssemblyDef manifest metadata table of the built assembly, and in the AssemblyRef table of any assembly that references it.

This is very important, because it means that when you reference a strongly named assembly, you are tightly bound to a specific AssemblyVersion of that assembly. The entire AssemblyVersion must be an exact match for the binding to succeed. For example, if you reference version of a strongly named assembly at build-time, but only version of that assembly is available at runtime, binding will fail! (You will then have to work around this using Assembly Binding Redirection.)

Confusion over whether the entire AssemblyVersion has to match. (Yes, it does.)

There is a little confusion around whether the entire AssemblyVersion has to be an exact match in order for an assembly to be loaded. Some people are under the false belief that only the Major and Minor parts of the AssemblyVersion have to match in order for binding to succeed. This is a sensible assumption, however it is ultimately incorrect (as of .NET 3.5), and it’s trivial to verify this for your version of the CLR. Just execute this sample code.

On my machine the second assembly load fails, and the last two lines of the fusion log make it perfectly clear why:

.NET Framework Version: 2.0.50727.3521
Attempting to load assembly: Rhino.Mocks, Version=, Culture=neutral, PublicKeyToken=0b3305902db7183f
Successfully loaded assembly: Rhino.Mocks, Version=, Culture=neutral, PublicKeyToken=0b3305902db7183f
Attempting to load assembly: Rhino.Mocks, Version=, Culture=neutral, PublicKeyToken=0b3305902db7183f
Assembly binding for  failed:
System.IO.FileLoadException: Could not load file or assembly 'Rhino.Mocks, Version=, Culture=neutral, 
PublicKeyToken=0b3305902db7183f' or one of its dependencies. The located assembly's manifest definition 
does not match the assembly reference. (Exception from HRESULT: 0x80131040)
File name: 'Rhino.Mocks, Version=, Culture=neutral, PublicKeyToken=0b3305902db7183f'

=== Pre-bind state information ===
LOG: User = Phoenix\Dani
LOG: DisplayName = Rhino.Mocks, Version=, Culture=neutral, PublicKeyToken=0b3305902db7183f
LOG: Appbase = [...]
LOG: Initial PrivatePath = NULL
Calling assembly : AssemblyBinding, Version=, 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\Framework64\v2.0.50727\config\machine.config.
LOG: Post-policy reference: Rhino.Mocks, Version=, Culture=neutral, PublicKeyToken=0b3305902db7183f
LOG: Attempting download of new URL [...].
WRN: Comparing the assembly name resulted in the mismatch: Revision Number
ERR: Failed to complete setup of assembly (hr = 0x80131040). Probing terminated.

I think the source of this confusion is probably because Microsoft originally intended to be a little more lenient on this strict matching of the full AssemblyVersion, by matching only on the Major and Minor version parts:

“When loading an assembly, the CLR will automatically find the latest installed servicing version that matches the major/minor version of the assembly being requested.” — Jeffrey Richter, CLR via C# (Second Edition) p. 56

This was the behaviour in Beta 1 of the 1.0 CLR, however this feature was removed before the 1.0 release, and hasn’t managed to re-surface in .NET 2.0:

“Note: I have just described how you should think of version numbers. Unfortunately, the CLR doesn’t treat version numbers this way. [In .NET 2.0], the CLR treats a version number as an opaque value, and if an assembly depends on version of another assembly, the CLR tries to load version only (unless a binding redirection is in place). However, Microsoft has plans to change the CLR’s loader in a future version so that it loads the latest build/revision for a given major/minor version of an assembly. For example, on a future version of the CLR, if the loader is trying to find version of an assembly and version exists, the loader with automatically pick up the latest servicing version. This will be a very welcome change to the CLR’s loader — I for one can’t wait.” — Jeffrey Richter, CLR via C# (Second Edition) p. 164 (Emphasis mine)

As this change still hasn’t been implemented, I think it’s safe to assume that Microsoft had back-tracked on this intent, and it is perhaps too late to change this now. I tried to search around the web to find out what happened with these plans, but I couldn’t find any answers. I still wanted to get to the bottom of it.

So I emailed Jeff Richter and asked him directly — I figured if anyone knew what happened, it would be him.

He replied within 12 hours, on a Saturday morning no less, and clarified that the .NET 1.0 Beta 1 loader did implement this ‘automatic roll-forward’ mechanism of picking up the latest available Build and Revision of an assembly, but this behaviour was reverted before .NET 1.0 shipped. It was later intended to revive this but it didn’t make it in before the CLR 2.0 shipped. Then came Silverlight, which took priority for the CLR team, so this functionality got delayed further. In the meantime, most of the people who were around in the days of CLR 1.0 Beta 1 have since moved on, so it’s unlikely that this will see the light of day, despite all the hard work that had already been put into it.

The current behaviour, it seems, is here to stay.

It is also worth noting from my discussion with Jeff that AssemblyFileVersion was only added after the removal of the ‘automatic roll-forward’ mechanism — because after 1.0 Beta 1, any change to the AssemblyVersion was a breaking change for your customers, there was then nowhere to safely store your build number. AssemblyFileVersion is that safe haven, since it’s never automatically examined by the CLR. Maybe it’s clearer that way, having two separate version numbers, with separate meanings, rather than trying to make that separation between the Major/Minor (breaking) and the Build/Revision (non-breaking) parts of the AssemblyVersion.

The bottom line: Think carefully about when you change your AssemblyVersion

The moral is that if you’re shipping assemblies that other developers are going to be referencing, you need to be extremely careful about when you do (and don’t) change the AssemblyVersion of those assemblies. Any changes to the AssemblyVersion will mean that application developers will either have to re-compile against the new version (to update those AssemblyRef entries) or use assembly binding redirects to manually override the binding.

  • Do not change the AssemblyVersion for a servicing release which is intended to be backwards compatible.
  • Do change the AssemblyVersion for a release that you know has breaking changes.

Just take another look at the version attributes on mscorlib:

// Assembly mscorlib, Version
[assembly: AssemblyFileVersion("2.0.50727.3521")]
[assembly: AssemblyInformationalVersion("2.0.50727.3521")]
[assembly: AssemblyVersion("")]

Note that it’s the AssemblyFileVersion that contains all the interesting servicing information (it’s the Revision part of this version that tells you what Service Pack you’re on), meanwhile the AssemblyVersion is fixed at a boring old Any change to the AssemblyVersion would force every .NET application referencing mscorlib.dll to re-compile against the new version!

What are differences between AssemblyVersion, AssemblyFileVersion and AssemblyInformationalVersion?


Where other assemblies that reference your assembly will look. If this number changes, other assemblies have to update their references to your assembly! The AssemblyVersion is required.

I use the format: major.minor. This would result in:

[assembly: AssemblyVersion("1.0")]


Used for deployment. You can increase this number for every deployment. It is used by setup programs. Use it to mark assemblies that have the same AssemblyVersion, but are generated from different builds.

In Windows, it can be viewed in the file properties.

If possible, let it be generated by MSBuild. The AssemblyFileVersion is optional. If not given, the AssemblyVersion is used.

I use the format: major.minor.revision.build, where I use revision for development stage (Alpha, Beta, RC and RTM), service packs and hot fixes. This would result in:

[assembly: AssemblyFileVersion("1.0.3100.1242")]


The Product version of the assembly. This is the version you would use when talking to customers or for display on your website. This version can be a string, like '1.0 Release Candidate'.

Unfortunately, when you use a string, it will generate a false warning -- already reported to Microsoft (fixed in VS2010). Also the Code Analysis will complain about it (CA2243) -- reported to Microsoft (not fixed in VS2013).

The AssemblyInformationalVersion is optional. If not given, the AssemblyFileVersion is used.

I use the format: major.minor [revision as string]. This would result in:

[assembly: AssemblyInformationalVersion("1.0 RC1")]

There is no hard and fast rule when it comes to versioning assemblies, so feel free to try which ever would work for you, but I would suggest you make use of 4 parts approach as you will have the flexiblity incase you want to make some changes in the future.

... for ex : 1.0.0.*

Reserved - This adds additional flexiblity, incase you want to make any change in future. But as a default, keep it as 0.

Also, consider signing the assembly with strong key. This will solve the assembly conflict issue incase you have multiple version of assembly registered in the GAC. MSDN Link

.net attributes versions