Problem: Unlike C#, the F# compiler allows multiple entries of assembly attributes
The C# compiler will not allow multiple entries of an assembly attribute within a project. If a duplicate exists, then CS0579 is emitted.
error CS0579: Duplicate 'AssemblyVersion' attribute
Unfortunately the F# compiler will allow multiple entries. This is a nuisance.
Why is this a problem?
I want to stamp every exe and dll in my solution with a version number and some other information, such as company name, title, description, trademark, copyright, etc.
Some of the information is individual for each project. I want that information to stay in AssemblyInfo.cs and AssemblyInfo.fs, which are the files initially generated when you create a project in VS. I edit this information manually in VS if needed.
Some of the information is shared among all projects of the solution. This is things like the version number, copyright information, company name, trademark, etc. I will create two files SharedAssemblyInfo.cs and SharedAssemblyInfo.fs on the solution level. Then every C# project will include SharedAssemblyInfo.cs by link, and every F# project will include SharedAssemblyInfo.fs by link. The build scripts will only have to modify these two files with updated version information before compilation, and restore them as was, after compilation. (To avoid any VCS seeing modifications, and committing them.)
Now comes the troublesome part.
If I create a new C# project, and include the SharedAssemblyInfo.cs by link, then the compiler will alert me of the duplicate assembly entries, and then I can remove the ones that are duplicate. This is quick to do, and I can't miss doing it. Good.
If I create a new F# project, and include the SharedAssemblyInfo.fs by link, then the compiler will not warn me of any duplicates. I have to remember that this needs doing. I also have to study the two files in order to determine which entries are duplicates. This is tedious and risky.
Expected behavior
The F# compiler should behave like the C# compiler in this regard, and not allow duplicate assembly attributes.
Actual behavior
The F# compiler allows duplicates of assembly attributes. This can cause problems to go unnoticed.
Known workarounds
A build script can use unit tests to analyze the build output for any inconsistencies in assembly attributes. This is however a rather time consuming solution to put in place in order to work around this problem.
Related information
VS 2017 and older. Severity: Minimal.
@BentTranberg fixing this would be a breaking change.
So we would probably need to add this as a disabled by default warning, then add to the templates a warnon:xxxx to enable it.
It's interesting, if the attributes are in the same source file it complains. If they are different it doesn't and takes the attribute value from the first file encountered.
I think it should be considered implemented even as a breaking change, because it's so easy to handle these cases. Maybe as a warning, but not disabled by default, because I think that complicates stuff. Compiler settings and pragmas and stuff like that should be kept to an absolute minimum in my opinion.
How many actual cases are there out there that would trip over this, and how many of them would actually be troublesome to fix? You are compiling and have the source available when it hits you, and it should be really easy to fix. Considering that file ordering plays a role here, it's a risk the way it is. I think anybody seeing a warning or error about this is far more likely to appreciate it than be troubled by it.
But I'm not in a good position to judge in these matters. Would be nice to have any way possible, but I guess this is less than trivial to most people. But I also feel F# should do no worse than C#.
Been thinking through this again. Could it be that it's not a problem, but a feature the way it is? If I put my SharedAssemblyInfo.fs at the bottom of the project (possibly before the file with the entry point), then I know it will overrule prior attributes. Then I actually don't have to edit the original AssemblyInfo.fs either. Maybe this issue isn't an issue?
Brent --- no it should be fixed. Having the attribute based on file ordering is confusing ... as you noted ... and something else that would change if we ever figure out how to remove the file ordering requirement.
If you are going to fix this, make it an optional change. I, and possibly others, have used this "bug" as a feature to make it easier to generate assembly infos. I.e., in a common project I have SharedAssemblyInfo.fs with AssemblyTitle attribute and many others. I link that (not a physical copy, but Add As Link) in every project.
If you then, just above that linked shared file, add another assemblyInfo.fs file, you can override the AssemblyTitle where necessary, or others, too (as @KevinRansom already mentioned, the override must be placed before).
This way, all projects share the same assembly and build version, same copyright notice, legal notices, company info etc, etc.

Note that not all assembly attributes can be applied only once. Some can be applied multiple times, depends on the attributes on the attribute class itself. I.e., compare AssemblyMetaDataAttribute, which can appear any number of times.
I'm not saying I'm opposed changing this, but keep in mind it is a great feature to have and my only gripe with it is that C# doesn't work the same way, leading to missing data if you forget to include an assembly attribute in the overriding file (which this F# feature prevents by allowing the use of defaults).
The fix, if we do it will be to add a warning, off by default. The project templates will be updated to enable it by default for new projects. It will be possible to edit the project to disable the warning for those projects.
That works for me and sounds excellent :)
What's the status of this? The .NET SDK >= 2.1.300 generates these attributes by default and I had a custom AssemblyInfo.fs file in my project. As my app attempted to read the AssemblyTitle attribute, it crashed because the attribute was definied multiple times.
It's interesting, if the attributes are in the same source file it complains. If they are different it doesn't and takes the attribute value from the first file encountered.
Then isn't this actually a bug? Also, it's not true that the compiler picks the first one, I always end up with multiple attributes in the assembly.
I got hit by this today. For a long time FAKE projects have generally managed their own AssemblyInfo.fs, which often look something like this:
// Auto-Generated by FAKE; do not edit
namespace System
open System.Reflection
[<assembly: AssemblyTitleAttribute("MYASSEMBLY
[<assembly: AssemblyProductAttribute("MYPRODUCT")>]
[<assembly: AssemblyDescriptionAttribute("Project has no summmary; update build.fsx")>]
[<assembly: AssemblyVersionAttribute("2.62.0")>]
[<assembly: AssemblyMetadataAttribute("date","7/12/18")>]
[<assembly: AssemblyFileVersionAttribute("2.62.0")>]
[<assembly: AssemblyInformationalVersionAttribute("2.62.0-beta014")>]
[<assembly: AssemblyMetadataAttribute("channel","beta")>]
do ()
module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "MYASSEMBLY"
let [<Literal>] AssemblyProduct = "MYPRODUCT"
let [<Literal>] AssemblyDescription = "Project has no summmary; update build.fsx"
let [<Literal>] AssemblyVersion = "2.62.0"
let [<Literal>] AssemblyMetadata_date = "7/12/18"
let [<Literal>] AssemblyFileVersion = "2.62.0"
let [<Literal>] AssemblyInformationalVersion = "2.62.0-beta014"
let [<Literal>] AssemblyMetadata_channel = "beta"
in order to have both the attributes as well as a quick string lookup of those values. Now I get duplicates of the attributes, so code looking for, say, AssemblyInformationalVersionAttribute has to get all attributes, due a typecheck+cast on the valid attributes, and then a Seq.tryLast in order to find the one that would actually apply.
Example showing duplicate attributes:
val it : seq<AssemblyInformationalVersionAttribute> =
seq
[System.Reflection.AssemblyInformationalVersionAttribute
{InformationalVersion = "1.0.0";
TypeId = System.Reflection.AssemblyInformationalVersionAttribute;};
System.Reflection.AssemblyInformationalVersionAttribute
{InformationalVersion = "2.62.0-beta014";
TypeId = System.Reflection.AssemblyInformationalVersionAttribute;}]
@baronfel
AssemblyInformationalAttribute, AssemblyTitle is not marked with the AttributeUsage AllowMultiple=false.
The default value for that is false.
Sources here: https://github.com/dotnet/corefx/blob/103639b6ff5aa6ab6097f70732530e411817f09b/src/Common/src/CoreLib/System/Reflection/AssemblyInformationalVersionAttribute.cs
https://github.com/dotnet/corefx/blob/103639b6ff5aa6ab6097f70732530e411817f09b/src/Common/src/CoreLib/System/Reflection/AssemblyTitleAttribute.cs
https://github.com/dotnet/corefx/blob/103639b6ff5aa6ab6097f70732530e411817f09b/src/Common/src/CoreLib/System/Reflection/AssemblyProductAttribute.cs
I'm going to assume the others follow the pattern.
Given the above, it is legal for the compiler to accept multiple copies of these attributes. And so I don't think there is anything for us to do.
If there are examples where an Attribute with AllowMultiple=false that appears multiple successfully compiles, then we should fix it. But as it stands, I don't think there is anything to do.
Does that sound about right?
@baronfel, apparently reading C# is too hard for me:
The default: is actually false, so it would seem there is a compiler bug., that we should fix, unfortunately we will have to fix it with a warning for existing projects and an error for new projects.
https://github.com/dotnet/corefx/blob/103639b6ff5aa6ab6097f70732530e411817f09b/src/Common/src/CoreLib/System/AttributeUsageAttribute.cs#L23
@KevinRansom thanks for taking a look there. I'll admit I was doubting my sanity for a moment :D
That mitigation plan sounds reasonable to me.
For anybody who wants to disable the automatically generated metadata file (for now), we can add a Directory.Build.props file in the root of the project with the GenerateAssemblyInfo property set to false:
<Project>
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
</Project>