dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.37k stars 4.75k forks source link

GetSatelliteAssembly() not throwing FileNotFoundException as documented #66656

Open NightOwl888 opened 2 years ago

NightOwl888 commented 2 years ago

Description

We are attempting to use Assembly.GetSatelliteAssembly(CultureInfo) followed by Assembly.GetManfestResourceStream() as a direct replacement for Assembly.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 using ResourceManager (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 return null if the satellite assembly doesn't exist so we can fall back ourselves, we were expecting a FileNotFoundException as per the documentation. However, what we discovered is that it will fall back to invariant culture rather than throwing FileNotFoundException.

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 a FileNotFoundException. 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 throw FileNotFoundException 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 the AssemblyResolve event, it should throw a FileNotFoundException.

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 the en-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 expecting null from the native code and will throw FileNotFoundException in that case, the native code is what seems to differ from the docs.

ghost commented 2 years ago

Tagging subscribers to this area: @vitek-karas, @agocke, @vsadov See info in area-owners.md if you want to be subscribed.

Issue Details
### Description We are attempting to use `Assembly.GetSatelliteAssembly(CultureInfo)` followed by `Assembly.GetManfestResourceStream()` as a direct replacement for `Assembly.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 using `ResourceManager` (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 return `null` if the satellite assembly doesn't exist so we can fall back ourselves, we were expecting a `FileNotFoundException` as per [the documentation](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getsatelliteassembly?view=net-6.0). However, what we discovered is that it will fall back to invariant culture rather than throwing `FileNotFoundException`. 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 a `FileNotFoundException`. 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](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getsatelliteassembly?view=net-6.0) states that `GetSatelliteAssembly(CultureInfo)` will throw `FileNotFoundException` 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 `/en-CA` directory, the GAC, or being provided by the `AssemblyResolve` event, it should throw a `FileNotFoundException`. ### Actual behavior If the `.resources.dll` file is missing from the `/en-CA` directory, no exception is thrown. The assembly returned is the `.resources.dll` file from ``. 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 the `en-CA\.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.5, .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 expecting `null` from the native code and will throw `FileNotFoundException` in that case, the native code is what seems to differ from [the docs](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getsatelliteassembly?view=net-6.0).
Author: NightOwl888
Assignees: -
Labels: `area-AssemblyLoader-coreclr`, `untriaged`
Milestone: -
ghost commented 2 years ago

Tagging subscribers to this area: @dotnet/area-system-resources See info in area-owners.md if you want to be subscribed.

Issue Details
### Description We are attempting to use `Assembly.GetSatelliteAssembly(CultureInfo)` followed by `Assembly.GetManfestResourceStream()` as a direct replacement for `Assembly.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 using `ResourceManager` (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 return `null` if the satellite assembly doesn't exist so we can fall back ourselves, we were expecting a `FileNotFoundException` as per [the documentation](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getsatelliteassembly?view=net-6.0). However, what we discovered is that it will fall back to invariant culture rather than throwing `FileNotFoundException`. 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 a `FileNotFoundException`. 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](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getsatelliteassembly?view=net-6.0) states that `GetSatelliteAssembly(CultureInfo)` will throw `FileNotFoundException` 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 `/en-CA` directory, the GAC, or being provided by the `AssemblyResolve` event, it should throw a `FileNotFoundException`. ### Actual behavior If the `.resources.dll` file is missing from the `/en-CA` directory, no exception is thrown. The assembly returned is the `.resources.dll` file from ``. 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 the `en-CA\.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 expecting `null` from the native code and will throw `FileNotFoundException` in that case, the native code is what seems to differ from [the docs](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getsatelliteassembly?view=net-6.0).
Author: NightOwl888
Assignees: -
Labels: `area-System.Resources`, `area-AssemblyLoader-coreclr`, `untriaged`
Milestone: -
ghost commented 2 years ago

Tagging subscribers to this area: @vitek-karas, @agocke, @vsadov See info in area-owners.md if you want to be subscribed.

Issue Details
### Description We are attempting to use `Assembly.GetSatelliteAssembly(CultureInfo)` followed by `Assembly.GetManfestResourceStream()` as a direct replacement for `Assembly.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 using `ResourceManager` (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 return `null` if the satellite assembly doesn't exist so we can fall back ourselves, we were expecting a `FileNotFoundException` as per [the documentation](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getsatelliteassembly?view=net-6.0). However, what we discovered is that it will fall back to invariant culture rather than throwing `FileNotFoundException`. 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 a `FileNotFoundException`. 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](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getsatelliteassembly?view=net-6.0) states that `GetSatelliteAssembly(CultureInfo)` will throw `FileNotFoundException` 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 `/en-CA` directory, the GAC, or being provided by the `AssemblyResolve` event, it should throw a `FileNotFoundException`. ### Actual behavior If the `.resources.dll` file is missing from the `/en-CA` directory, no exception is thrown. The assembly returned is the `.resources.dll` file from ``. 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 the `en-CA\.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 expecting `null` from the native code and will throw `FileNotFoundException` in that case, the native code is what seems to differ from [the docs](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getsatelliteassembly?view=net-6.0).
Author: NightOwl888
Assignees: -
Labels: `area-System.Resources`, `area-AssemblyLoader-coreclr`, `untriaged`
Milestone: -
vitek-karas commented 2 years ago

Linking the relevant docs: https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/loading-resources

This will need detailed investigation...