Description
Specifically:
ECMA-335 § II.23.3
- If the parameter kind is System.Type, (also, the middle line in above diagram) its is stored as a SerString (as defined in the previous paragraph), representing its name. The canonical name is its full type name, followed optionally by assembly where it is defined, its version, culture and public-key-token. If the name is omitted, the CLI looks first in the current assembly, and then in the system library (mscorlib); in these two special cases, it is permitted to omit the name, version, culture and public-key-token.
(emphasis mine)
The spec calls out mscorlib
here, which was reasonable for .NET Framework. However, in .NET Core and .NET Standard, the corelib is no longer in mscorlib
, but rather (in the API contract) System.Runtime
or netstandard
primarily. CoreCLR does not use any of these, however, and instead uses System.Private.CoreLib
as the fallback, despite that being a (supposedly) private implementation detail. This is most notably a problem for System.Uri
: it is in the reference corelib (System.Uri
) on .NET Core and .NET Standard, but its implementation is in System.Private.Uri
, not System.Private.CoreLib
, so a CA blob that contains System.Uri
will fail to resolve!
More detail on where this was found:
For some additional references for behavior being changed by this PR, all checked 2025-06-12:
ECMA-335 6th Edition, Section II.23.3, bottom of page 268, describes how to do resolution when no assembly spec is given for Type arguments: https://ecma-international.org/wp-content/uploads/ECMA-335_6th_edition_june_2012.pdf
The current behavior of CoreCLR, which attempts current assembly, then
typeof(object).Assembly
(System.Private.CoreLib), then the TypeResolve event of the current AssemblyLoadContext:The current behavior of Roslyn (the C#/VB compiler), which always writes FQNs when there is an assembly being referenced: https://github.com/dotnet/roslyn/blob/2f32bf87e211fe5319c94f442c4ad56254bc75e0/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs#L194
Cecil reading always uses
module.TypeSystem.CoreLibrary
when it cannot find a type definition (the current behavior of AsmResolver): https://github.com/jbevain/cecil/blob/3136847ea620fb9b4a3ff96bc4f573148e8bd2e4/Mono.Cecil/TypeParser.cs#L356Cecil writing follows the ECMA section to the letter and only omits it when the assembly being referenced is exactly current module or mscorlib (this behavior is absolutely incorrect and almost certainly has real issues, I just cannot be bothered to synthesize examples of it, this fails when referencing mscorlib for any type that is not today in System.Private.CoreLib and then loading that attribute with CoreCLR): https://github.com/jbevain/cecil/blob/3136847ea620fb9b4a3ff96bc4f573148e8bd2e4/Mono.Cecil/TypeParser.cs#L521
dnLib reading does the same behavior as current AsmResolver and Cecil, attempting current module, then the referenced corlib: https://github.com/0xd4d/dnlib/blob/c78d296c522aae0520df2afd825d48266321cf36/src/DotNet/CustomAttributeReader.cs#L24
dnLib writing has an option flag for whether to enable the same optimization that AsmResolver currently employs: https://github.com/0xd4d/dnlib/blob/c78d296c522aae0520df2afd825d48266321cf36/src/DotNet/Writer/Metadata.cs#L182-L196
Even Cecil is technically wrong here, with respect to what CoreCLR does! The only actually correct thing to do is what Roslyn does, but that poses a problem when trying to load potentially poorly authored assemblies, because there's not a good answer for what to do.
Proposed solution(s)
- Formally deprecate (e.g. in an ECMA augment) omitting the assembly for corelib types
- Make CoreCLR (and Mono, if necessary) resolve such types through the
mscorlib
forwarder assembly (and maybe other forwarder assemblies depending on the source assembly's defined targetframework?)
- .NET Framework assemblies authored according to spec will load properly, though there is a risk that some incorrectly-authored .NET Standard or .NET Core assemblies will fail to resolve properly without checking the defined TargetFramework
Alternatively:
Augment ECMA to specify resolving-through
System.Runtime
instead ofmscorlib
, and adjust CoreCLR and Mono appropriately
- This sucks, because not specifying the assembly sucks in the first place.
A slightly more out-there idea is to rev the CA blob format and deprecate the string form entirely, replacing it with an encoded TypeDef/TypeRef/TypeSpec token and using all of the goodness of the .NET metadata format here, too.