【问题标题】:Invalid CultureInfo no longer throws CultureNotFoundException无效的 CultureInfo 不再抛出 CultureNotFoundException
【发布时间】:2016-01-28 23:19:30
【问题描述】:

使用es-CA 创建文化信息,这显然不正确应该抛出异常,但现在不会。

这个以前抛出了一个CultureNotFoundExceptionnew CultureInfo("es-CA")。它现在似乎回退到 es 并带有“未知语言环境”。虽然,像xy-ZZ 这样的操作也可以,这很奇怪吗?

为什么这不再抛出异常?这在 .NET 的最新版本中是否发生了变化?

更新 1

文档提到以下内容:

如果操作系统不支持该文化,并且如果 name 不是补充或替代文化的名称,则该方法将引发 CultureNotFoundException 异常。

在 Windows 7 上对此进行测试,它会抛出 CultureNotFoundException,但在 Windows 10 上它不会抛出异常。

【问题讨论】:

  • 它必须存在于您的机器上,因为行为没有改变 (msdn.microsoft.com/en-us/library/ky2chs3h(v=vs.110).aspx)。
  • @BalinKingOfMoria,我是说它不会抛出异常,它应该抛出异常。我的机器上不存在该文化或本地文化。
  • 我知道,但我只是想让您知道,文档说它的行为仍然如此。
  • @BalinKingOfMoria,谢谢!看起来是因为操作系统...这有点烦人。
  • @LexLi 你可能指的是这个:github.com/dotnet/corefx/issues/1669

标签: c# .net globalization


【解决方案1】:

现在根据 cmets 添加一个答案。

由于 Windows 设计的更改,如果名称与 BCP-47 匹配,现在不再有“无效文化”,因此 .NET Framework/.NET Core 不会抛出异常,而是接受新文化。

您可以参考the GitHub discussion,以及下面的报价,

由于框架依赖于操作系统来获取文化,因此操作系统的 正在向模型迁移任何 BCP-47 文化名称,即使是 操作系统不支持它。例如,Windows 10 支持任何 格式良好的文化名称,即使操作系统也没有此类的真实数据 文化。例如,如果试图在 Windows 10 它将成功。考虑到这一点,没有意义 有文化枚举,因为我们返回的任何集合并不意味着这些是 唯一支持的文化。查看您的问题,您的解决方法是 可以接受。如果真的想有更多更好的方法,我们可以考虑 类似于 CultureInfo.TryGetCulture() 但正如我之前所说 向前发展,几乎任何文化都是有效的。

【讨论】:

  • 您好,我一直为我的一个单元测试开始失败而烦恼。如果解析被认为是有效的“not-a-valid-culture”。如果维基百科(en.wikipedia.org/wiki/IETF_language_tag#Syntax_of_language_tags)是正确的,那么这应该仍然是一种无效的文化。无论如何,感谢您的回复。我了解到我们必须为我们的文化推出自己的验证。
  • 天哪,猫头鹰蝙蝠侠,这意味着您可以在一个环境中创建无效的new Culture("flippidyfloop")而没有异常,而在另一个环境中它会抛出异常。
【解决方案2】:

我将其发布为对 OP 问题帖子中几乎提出的两个问题的回答:

在 Windows 10 进行重大更改以支持 BCP-47...

  1. 如何判断给定的 CultureInfo 对象是“真实”文化,还是从头开始在代码中创建的虚假/人为/私有 CultureInfo
  2. 我如何判断用户提供的 String cultureName 值是否对 new CultureInfo(String) 有效并且运行时环境(.NET 和/或 OS)具有该名称的有意义的文化数据(不仅仅是DisplayName)?

问题 1:验证给定的 CultureInfo 实例:

As per the documentation for CultureTypes, prior to Windows 10,如果CultureInfo.CultureTypes 属性具有UserCustomCulture 标志,那么它是一种自定义文化。从 Windows 10 开始,UserCustomCulture 标志表示自定义文化,但也表示“没有完整的文化数据集支持的系统文化并且没有唯一的本地标识符”。

因此,如果您想在 Windows 10 上验证CultureInfo完全相同,就像在 Windows 8.1 或更早版本上一样,只需检查:

  1. CultureInfo.CultureTypes 没有设置 CultureTypes.UserCustomCulture 标志。
  2. 如果它确实UserCustomCulture,请确保CultureInfo.ThreeLetterWindowsLanguageName != "ZZZ"
    • "ZZZ" 魔法字符串似乎在 Windows 本身中,它只出现在 Windows 10 或更高版本上。
    • .NET Core's own test-cases includes a test for it,但从未在评论“.GetThreeLetterWindowsLanguageName(cultureName) ?? "ZZZ" /* default lang name */;”之外解释它。

所以这对我有用:

public static Boolean ValidateCultureInfoWithPreWindows10Logic( CultureInfo ci )
{
    Boolean hasUserCustom = ( ci.CultureTypes & CultureTypes.UserCustomCulture ) == CultureTypes.UserCustomCulture;
    if( hasUserCustom )
    {
        if( ci.ThreeLetterWindowsLanguageName == "ZZZ" )
        {
            // Windows doesn't have a name for this language - this CultureInfo is invalid under Windows 8.1 or earlier.
            return false;
        }
        else
        {
            // The `UserCustomCulture` flag means *some* CultureData is missing, but not enough to make them useless.
            // On both Win8 and Win10, the same 8 Neutral Cultures match here: [ jv, jv-Latn, mg, nqo, sn, sn-Latn, zgh, zgh-Tfng ]
            return true;
        }
    }
    else
    {
        // The `UserCustomCulture` flag is not set, which means 100% of the CultureInfo's CultureData exists in the system.
        return true;
    }
}

问题 2:验证给定的String cultureName

  • 请记住,文化名称是分层的,有 3 个主要级别:

    • Invariant = CultureInfo.InvariantCulture
    • Neutral = 没有区域的语言名称,例如enfr
    • Specific = 特定区域的语言名称,例如en-USen-GBfr-CAfr-FR
    • 此外,还有一些子特定区域的名称,例如ca-ES-valencia。不过,我从未遇到过超过 3 级的深度。
  • 验证cultureName 取决于您的业务/域/应用程序要求:

    • 如果您想要求名称与操作系统已知的语言区域匹配,那么执行ValidateCultureInfoWithPreWindows10Logic( new CultureInfo( cultureName ) ) 就足够了(在验证cultureName 的格式符合BCP-47 之后,当然)。
    • 如果您希望名称与操作系统已知的语言匹配,但允许指定任何操作系统已知的区域,即使操作系统没有特定的 CultureData(例如当使用CultureInfo.CreateSpecificCulture("en-FR")) 时,检查ci.ThreeLetterWindowsLanguageName != "ZZZ" 就足够了。
    • 如果您想要求名称与操作系统已知的语言相匹配,但允许指定任何区域,即使操作系统甚至不知道该区域,这也很复杂。 ..

下表显示了 new CultureInfoCultureInfo.CreateSpecificCulture 在 Windows 10 与 Server 2012 R2 以及 .NET 4.8 与 .NET 6 上的结果:

Expression Windows 10 + .NET 6 Windows 10 + .NET 4.8 Windows 2012 R2 + .NET 4.8
CultureInfo ci1 = new CultureInfo("en-FR")
ci1.DisplayName "English (France)" "Unknown Locale (en-FR)" CultureNotFoundException
ci1.ThreeLetterWindowsLanguageName "ZZZ" "ENU" CultureNotFoundException
ci1.CultureTypes SpecificCultures | UserCustomCulture | InstalledWin32Cultures SpecificCultures | UserCustomCulture CultureNotFoundException
CultureInfo spec = CultureInfo.CreateSpecificCulture("en-FR")
spec.DisplayName "English (France)" "Unknown Locale (en-FR)" "English (United States)"
spec.ThreeLetterWindowsLanguageName "ZZZ" "ENU" "ENU"
spec.CultureTypes SpecificCultures | UserCustomCulture | InstalledWin32Cultures SpecificCultures | UserCustomCulture SpecificCultures | InstalledWin32Cultures | FrameworkCultures

到目前为止,非常不一致。

如果您想允许任意语言名称,即使操作系统不了解该语言(更不用说该地区) - 无论是中性还是特定 CultureInfo...呃...我必须回答稍后再问。


其他提示:当您希望 cultureName 仅限于操作系统支持的文化(中性和/或特定)时,如何可靠地验证它:

快速解决方法是:

public static class KnownCultureInfoNameValidator
{

private static readonly HashSet<String> _preWindows10BuiltInCustomNames = new String[]
{
     "jv", "jv-Latn", "mg", "nqo", "sn", "sn-Latn", "zgh", "zgh-Tfng"
}
    .ToHashSet();

private static readonly HashSet<String> _knownLanguages = BuildHashSet( CultureInfo.GetCultures( CultureTypes.NeutralCultures ) );

private static readonly HashSet<String> _knownSpecific = BuildHashSet( CultureInfo.GetCultures( CultureTypes.SpecificCultures ) );

private static HashSet<String> BuildHashSet( IEnumerable<CultureInfo> cultures )
{
    return cultures
        .Where( ci => ci.ThreeLetterWindowsLanguageName != "ZZZ" )
        .Where( ci => ci.LCID != 127 ) // Exclude InvariantCulture
#if LIKE_PRE_WINDOWS_10
        .Where( ci =>
            _preWindows10BuiltInCustomNames.Contains( ci.Name )
            ||
            ( ci.CultureTypes & CultureTypes.UserCustomCulture ) == 0
        )
#endif
        .Select( ci => ci.Name )
        .ToHashSet();
}

// Only returns true if `cultureName` is an OS-known culture with sufficient OS-provided culture data. This method will return false for partially-known cultuires.
public static Boolean ValidateCultureName( String cultureName, Boolean allowNeutral, Boolean allowSpecific )
{
     if( allowNeutral && _knownLanguages.Contains( cultureName ) ) return true;

     if( allowSpecific && _knownSpecific.Contains( cultureName ) ) return true;

     return false;
}

}

研究:

  • 我一直在研究 .NET 的 CultureInfo 和(内部)CultureData 的内部结构,这是我的发现:

    • 当使用任何String name 构造函数(包括内部构造函数)创建new CultureData 实例时,会创建一个新的empty CultureData 对象,然后创建它的sRealName 和@987654374使用来自CultureInfo 的构造函数调用站点的较早的cultureNameuseUserOverride 值(分别)设置的@ 字段。

    • 这个CultureData 就是passed into a function nativeInitCultureData that's internal to the .NET CLR runtime(即MethodImplOptions.InternalCall)。

      • 注意 .NET Framework 4.8 代码(即上面的 referencesource.microsoft.com 链接)代码基本上假定 sWindowsNameCultureInfo 的正式名称,所以如果它不是 null 然后 当然它一定很好(例如DoGetLocaleInfo):

        int DoGetLocaleInfoInt(uint lctype)
        {
           // Ask OS for data, note that we presume it returns success, so we have to know that
           // sWindowsName is valid before calling.
           Contract.Assert(this.sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected this.sWindowsName to be populated by COMNlsInfo::nativeInitCultureData already");
           int result = CultureInfo.nativeGetLocaleInfoExInt(this.sWindowsName, lctype);
        
            return result;
        }
        
    • 所以我们不能使用CultureInfo.LCID,因为现在4096 == 0x1000 用于系统提供但部分的CultureInfo 对象 - 就像它用于“假”CultureInfo 对象一样。

    • 我们也不能使用CultureInfo.CompareInfo.LCID,因为那里仍然有很多“真实的”(但也不完整的)系统提供的带有0x1000 的文化,例如

    • 因此,因为 Windows 10 现在总是在使用任何符合 BCP-47 的输入 cultureName 时为 sWindowsName 返回非null 字符串值,这就是为什么没有即时方法来检测“假”与“ .NET 中的真实“CultureInfo 对象。

  • 这意味着现在只有两种方法可以检查给定的 CultureInfo 是“假”还是“真”:

    • 选项 1: 在程序启动期间,从 CultureInfo.GetCultures 构建您自己的私有不可变 CultureInfo 名称 HashSet&lt;String&gt; 并使用它来验证,请参阅上面的 KnownCultureInfoNameValidator
    • 选项 2: 检查 CultureInfo.EnglishName 是否以 "Unknown Locale" 开头和/或 CultureInfo.Parent.EnglishName 是否以 "Unknown Language" 开头。
      • 虽然比较 魔术字符串 总是感觉不对,尤其是人类可读的字符串,但至少 EnglishName 始终是英文的,并且在用户运行非英文版本时不会中断例如,Windows 与 Exception.Message 不同。
      • 似乎没有任何其他记录提示或值提示CultureInfo 的数据是否真正系统提供。 CultureData 的其他非 String 成员似乎都没有这样做。
      • 一定要使用String.StartsWith检查,不要 String.Equals,因为末尾有括号CultureName
      • 我最初确实认为 ThreeLetterWindowsLanguageName == "ZZZ" 可能有效,但在我的计算机上,CultureInfo.GetCultures 方法返回 114 种中性文化和 326 种特定文化,并为该属性提供 "ZZZ" 值,呃。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-04
    • 1970-01-01
    • 2020-01-13
    • 2014-09-26
    • 2013-02-13
    • 2021-01-18
    • 1970-01-01
    相关资源
    最近更新 更多