Open NightOwl888 opened 2 years ago
Tagging subscribers to this area: @vitek-karas, @agocke, @vsadov See info in area-owners.md if you want to be subscribed.
Author: | NightOwl888 |
---|---|
Assignees: | - |
Labels: | `area-AssemblyLoader-coreclr`, `untriaged` |
Milestone: | - |
Tagging subscribers to this area: @dotnet/area-system-resources See info in area-owners.md if you want to be subscribed.
Author: | NightOwl888 |
---|---|
Assignees: | - |
Labels: | `area-System.Resources`, `area-AssemblyLoader-coreclr`, `untriaged` |
Milestone: | - |
Tagging subscribers to this area: @vitek-karas, @agocke, @vsadov See info in area-owners.md if you want to be subscribed.
Author: | NightOwl888 |
---|---|
Assignees: | - |
Labels: | `area-System.Resources`, `area-AssemblyLoader-coreclr`, `untriaged` |
Milestone: | - |
Linking the relevant docs: https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/loading-resources
This will need detailed investigation...
Description
We are attempting to use
Assembly.GetSatelliteAssembly(CultureInfo)
followed byAssembly.GetManfestResourceStream()
as a direct replacement forAssembly.GetManfestResourceStream()
. We already have a caching and fallback mechanism that we want to re-use, and there seems to be a lot of extra baggage with usingResourceManager
(namely re-packing the file streams into a.resources
file instead of using embedded files in the satellite assembly).While the ideal solution would be for
Assembly.GetSatelliteAssembly(CultureInfo)
to returnnull
if the satellite assembly doesn't exist so we can fall back ourselves, we were expecting aFileNotFoundException
as per the documentation. However, what we discovered is that it will fall back to invariant culture rather than throwingFileNotFoundException
.Being that we can simply do a check to see if the culture is correct and return
null
from here, this seems to be a better solution than catching aFileNotFoundException
. But, being that it is not documented that way, we are hesitant to rely 100% on the observed behavior, especially being that it might not match other platforms that .NET Standard 2.0 supports.It is possible that we have missed a step when packing our embedded resources that is causing it to misbehave. To keep it simple, we left that stage out of the repro project, but you can view the
LinkAssembly
inline task we made here: https://dev.azure.com/shad0962/Experiments/_git/ICU4N?version=GBfeature/resource-automation&path=/src/ICU4N/ICU4N.csproj. However, I presume you can glean enough info about the satellite assemblies by viewing the metadata to ensure they are built correctly.Assembly Metadata
Click to expand!
### /en-US/ICU4N.resources.dll ```c# using System.Reflection; // Assembly ICU4N.resources, Version=60.0.0.0, Culture=en-US, PublicKeyToken=efb17c8e4f0e291b // MVID: DCBF01E1-8DAC-40A3-A905-D2A61F598974 // Assembly references: // mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 [assembly: AssemblyTitle("ICU4N")] [assembly: AssemblyDescription("ICU (International Components for Unicode) is a set of libraries providing Unicode and Globalization support for software applications. It provides Text-boundary analysis (RuleBasedBreakIterator) as well as easy access to all of the many Unicode character properties, Unicode Normalization, Case Folding and other fundamental operations as specified by the Unicode Standard. ICU4N is a .NET port of ICU4J.")] [assembly: AssemblyCompany("ICU4N")] [assembly: AssemblyProduct("ICU4N")] [assembly: AssemblyInformationalVersion("60.1.0-alpha.381+20d113ebd5")] [assembly: AssemblyCopyright("Copyright © 2019 - 2022 ICU4N")] [assembly: AssemblyFileVersion("60.1.0")] [assembly: AssemblyVersion("60.0.0.0")] ``` ### /en/ICU4N.resources.dll ```c# using System.Reflection; // Assembly ICU4N.resources, Version=60.0.0.0, Culture=en, PublicKeyToken=efb17c8e4f0e291b // MVID: 35D07924-AD2A-46BE-BC22-41ABAB6ACED5 // Assembly references: // mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 [assembly: AssemblyTitle("ICU4N")] [assembly: AssemblyDescription("ICU (International Components for Unicode) is a set of libraries providing Unicode and Globalization support for software applications. It provides Text-boundary analysis (RuleBasedBreakIterator) as well as easy access to all of the many Unicode character properties, Unicode Normalization, Case Folding and other fundamental operations as specified by the Unicode Standard. ICU4N is a .NET port of ICU4J.")] [assembly: AssemblyCompany("ICU4N")] [assembly: AssemblyProduct("ICU4N")] [assembly: AssemblyInformationalVersion("60.1.0-alpha.381+20d113ebd5")] [assembly: AssemblyCopyright("Copyright © 2019 - 2022 ICU4N")] [assembly: AssemblyFileVersion("60.1.0")] [assembly: AssemblyVersion("60.0.0.0")] ``` ### /ICU4N.resources.dll ```c# using System.Reflection; // Assembly ICU4N.resources, Version=60.0.0.0, Culture=neutral, PublicKeyToken=efb17c8e4f0e291b // MVID: 7CE159F0-78E8-4245-8B2C-81B1F12CB03D // Assembly references: // mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 [assembly: AssemblyTitle("ICU4N")] [assembly: AssemblyDescription("ICU (International Components for Unicode) is a set of libraries providing Unicode and Globalization support for software applications. It provides Text-boundary analysis (RuleBasedBreakIterator) as well as easy access to all of the many Unicode character properties, Unicode Normalization, Case Folding and other fundamental operations as specified by the Unicode Standard. ICU4N is a .NET port of ICU4J.")] [assembly: AssemblyCompany("ICU4N")] [assembly: AssemblyProduct("ICU4N")] [assembly: AssemblyInformationalVersion("60.1.0-alpha.381+20d113ebd5")] [assembly: AssemblyCopyright("Copyright © 2019 - 2022 ICU4N")] [assembly: AssemblyFileVersion("60.1.0")] [assembly: AssemblyVersion("60.0.0.0")] ``` ### /ICU4N.dll ```c# using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; // Assembly ICU4N, Version=60.0.0.0, Culture=neutral, PublicKeyToken=efb17c8e4f0e291b // MVID: 3A915DD2-2927-4684-9D8C-1FDD4EC46152 // Assembly references: // netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("ICU4N")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyCopyright("Copyright © 2019 - 2022 ICU4N")] [assembly: AssemblyDescription("ICU (International Components for Unicode) is a set of libraries providing Unicode and Globalization support for software applications. It provides Text-boundary analysis (RuleBasedBreakIterator) as well as easy access to all of the many Unicode character properties, Unicode Normalization, Case Folding and other fundamental operations as specified by the Unicode Standard. ICU4N is a .NET port of ICU4J.")] [assembly: AssemblyFileVersion("60.1.0")] [assembly: AssemblyInformationalVersion("60.1.0-alpha.381+20d113ebd5")] [assembly: AssemblyProduct("ICU4N")] [assembly: AssemblyTitle("ICU4N")] [assembly: AssemblyVersion("60.0.0.0")] ```Reproduction Steps
I have created a repro project here: https://github.com/NightOwl888/GetSatelliteAssemblyIssue. Simply clone it and run the tests.
Expected behavior
The documentation states that
GetSatelliteAssembly(CultureInfo)
will throwFileNotFoundException
if "the assembly cannot be found".It is not completely clear what that means. I presume that means the assembly was scanned for in all of the normal locations (including the GAC and convention-based directories) and it couldn't be found. Therefore, when using the
en-CA
culture, and it cannot find an assembly for that culture in either the<appDir>/en-CA
directory, the GAC, or being provided by theAssemblyResolve
event, it should throw aFileNotFoundException
.Actual behavior
If the
.resources.dll
file is missing from the<appDir>/en-CA
directory, no exception is thrown. The assembly returned is the.resources.dll
file from<appDir>
.This behavior is illogical, since it neither matches with the docs, nor does it fall back to
en
.That being said, an acceptable fix for us would be to confirm it works this way on every platform that
.NET Standard
supports and update the documentation to state it works this way. If we can rely on this contract, it is better than having to deal with exceptions in what can be considered the "normal" flow. That is, the end user decided not to distribute theen-CA\<AssemblyName>.resources.dll
file with their distribution, which is a "normal" use case that would ideally not throw exceptions.If you update the documentation, we need to know what cases (if any) a
FileNotFoundException
will be thrown. It should also clearly state that the neutral language assembly will be returned in the case where the satellite assembly DLL doesn't exist.Regression?
No response
Known Workarounds
No response
Configuration
The behavior appears to be consistent on .NET Framework 4.5.2, .NET Framework 4.6.1, .NET Framework 4.8, .NET Core 3.1, .NET 5, and .NET 6.
We have confirmed that the behavior is consistent on Linux, macOS, and Windows in both x86 and x64.
We don't know whether the same behavior exists on other platforms, and can't be sure it will be consistent because the behavior doesn't match the docs.
Other information
Given that the
RuntimeAssembly
class is apparently expectingnull
from the native code and will throwFileNotFoundException
in that case, the native code is what seems to differ from the docs.