如何检查.NET中是否存在特定的文化设置

25

我有这段代码,当我尝试获取不存在的文化时,会抛出异常。
是否存在像TryGetCultureInfo这样返回bool值的方法?我不想使用try-catch语句。

CultureInfo culture = CultureInfo.GetCultureInfo(cultureCode);
if (culture == null)
{
    culture = CultureInfo.GetCultureInfo(DefaultCultureCode);
}

9
为什么不想使用try-catch语句?这是try/catch的一个很好的应用场景。 - Jon
4
@Jon和shiplu.mokadd.im - 避免使用try/catch是有很好的理由的。我寻求这个问题的答案,因为我的应用枚举了我的bin文件夹的子目录,并尝试确定该目录名称是否是有效的文化名称(表明它包含卫星资源),以便应用程序可以在下拉菜单中包括有效的文化。 这不是期望值是文化的情况。现在我的调试器一直在这个不必要的异常上停止。 - DannyMeister
1
@DannyMeister:听起来你需要在调试器中禁用“首次机会异常”。在你的情况下,我也会避免使用try/catch,但这是因为你提供了额外的上下文。没有上下文就没有充分的理由避免使用它。 - Jon
@Jon,作者对异常的要求是有道理的。抛出和捕获异常会带来开销。如果可以避免,那就应该避免。不幸的是,在这种情况下似乎没有其他(有效)方法可以做到这一点,除了捕获“CultureNotFoundException”。 - Crono
1
不,你不能这样做。但是我升级到 .net 6 后发现 CultureInfo 构造函数不再抛出异常了。 我尝试了下面的建议,但它们是不完整的。我发现许多有效的字符串在检查时失败,但在使用 new CultureInfo("") 时是有效的,例如 "no"。 唯一的方法是测试返回的 CultureInfo 中的属性,使用一个无效的名称来生成它。 - Norcino
显示剩余4条评论
6个回答

38

您可以编写一个返回布尔值的DoesCultureExist方法,就像这样:

private static bool DoesCultureExist(string cultureName)
{
    return CultureInfo.GetCultures(CultureTypes.AllCultures).Any(culture => string.Equals(culture.Name, cultureName, StringComparison.CurrentCultureIgnoreCase));
}

4
我已经帮你编辑了代码,使其忽略大小写,这是 CultureInfo 通常的工作方式。 - Crono

22

我认为没有这样的方法。因此,您可以只使用try-catch或检查所有已安装的区域设置:

string cultureCode = "de-DE";
CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures & ~CultureTypes.NeutralCultures);
var culture = cultures.FirstOrDefault(c => c.Name.Equals(cultureCode, StringComparison.OrdinalIgnoreCase));
if (culture == null)
{
    culture = cultures.FirstOrDefault(c => c.Name.Equals(DefaultCultureCode, StringComparison.OrdinalIgnoreCase));
    if (culture == null)
        culture = CultureInfo.CurrentCulture;
}

但我更喜欢 try-catch,我相信它更有效率。

public bool TryGetCultureInfo(string cultureCode, string DefaultCultureCode, out CultureInfo culture)
{
    try
    {
        culture = CultureInfo.GetCultureInfo(cultureCode);
        return true;
    } catch(CultureNotFoundException)
    {
        if (DefaultCultureCode == null)
            culture = CultureInfo.CurrentCulture;
        else
        {
            try
            {
                culture = CultureInfo.GetCultureInfo(DefaultCultureCode);
            } catch (CultureNotFoundException)
            {
                culture = CultureInfo.CurrentCulture;
            }
        }
    }
    return false;
}

3
捕获特定的异常类型。 - Aidan Ryan
3
因为你只想要捕获 CultureNotFoundException 异常,所以类型很重要。还有许多其他可能出现的异常,比如 ArgumentNullException 或者 OutOfMemoryException,你可能不想在这里捕获它们。另外,如果你不给参数命名,就没有人会认为你在使用该异常 —— catch (CultureNotFoundException) { culture = CultureInfo.CurrentCulture; }。这展示了你准确的目的以及你期望发生的情况。 - Aidan Ryan
2
@AidanRyan:不知怎么错过了CultureNotFoundException ;) 好的,我明白了,已经编辑了答案。谢谢。 - Tim Schmelter
3
希望指出,在.NET 3.5及更早版本中,引发的异常确实是ArgumentException。CultureNotFoundException仅用于4+版本。 - Rich
5
CultureInfo.GetCultureInfo("asd"); 不会抛出异常。因此,这个答案是错误的! - Dominic Jonas
显示剩余5条评论

14

如果想要速度更快,可以使用:

internal static class Culture
{
    private static readonly HashSet<string> CultureNames = CreateCultureNames();

    internal static bool Exists(string name)
    {
        return CultureNames.Contains(name);
    }

    private static HashSet<string> CreateCultureNames()
    {
        var cultureInfos = CultureInfo.GetCultures(CultureTypes.AllCultures)
                                      .Where(x => !string.IsNullOrEmpty(x.Name))
                                      .ToArray();
        var allNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        allNames.UnionWith(cultureInfos.Select(x => x.TwoLetterISOLanguageName));
        allNames.UnionWith(cultureInfos.Select(x => x.Name));
        return allNames;
    }
}

1
这应该是首选答案,因为应避免使用异常作为流程控制。 - Sava B.
谢谢您。简单而有效。 - Smorkster
zh-MO、no 等字符串在作为构造函数参数使用 CultureInfo 时是有效的,这样可以得到正确的文化。因此,这个解决方案中缺少了一些东西,但还是非常感谢。 - Norcino

3
不,据我所知是不可能的。您可以首先检查文化是否存在,如果存在,则获取它。
以下代码展示了如何实现:
    private static CultureInfo GetCulture(string name)
    {
        if (!CultureExists(name)) return null;

        return CultureInfo.GetCultureInfo(name);
    }

    private static bool CultureExists(string name)
    {
        CultureInfo[] availableCultures =
            CultureInfo.GetCultures(CultureTypes.AllCultures);

        foreach (CultureInfo culture in availableCultures)
        {
            if (culture.Name.Equals(name))
                return true;
        }

        return false;
    }

希望这能有所帮助。


2

简而言之

接受的答案速度较慢。你可以尝试另一种解决方案以获得更好的性能:在try-catch语句中使用CultureInfo.GetCultureInfo

建议的解决方案

尽管try-catch通常不是一个好主意,但我仍建议在catch CultureNotFoundException子句中使用CultureInfo.GetCultureInfo

最重要的是,GetCultureInfo具有线程安全的缓存的文化名称,因此每次调用时都不需要查找。

代码

public static bool Exists(string name)
{
    try
    {
        CultureInfo.GetCultureInfo(name);
        return true;
    }
    catch (CultureNotFoundException)
    {
        return false;
    }
}

基准测试

我进行了一个基准测试,以检查哪种方法更快,而GetCultureInfo明显(超过4000倍)更快。

结果:

BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 11 (10.0.22621.2506/22H2/2022Update/SunValley2)
12th Gen Intel Core i5-12400F, 1 CPU, 12 logical and 6 physical cores
.NET SDK 7.0.403
  [Host]     : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
  DefaultJob : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2


| Method                  | Mean         | Error        | StdDev       |
|------------------------ |-------------:|-------------:|-------------:|
| GetCultureInfo_TryCatch |     19.88 ns |     0.372 ns |     0.330 ns |
| GetCultures             | 83,831.36 ns | 1,638.645 ns | 2,599.065 ns |

基准测试代码

[SimpleJob]
public class CultureExistsBenchmarks
{
    [Benchmark]
    public void GetCultureInfo_TryCatch()
    {
        try { CultureInfo.GetCultureInfo("zzz"); }
        catch (CultureNotFoundException) { }
    }
    
    [Benchmark]
    public void GetCultures() => 
        CultureInfo.GetCultures(CultureTypes.AllCultures).Any(culture => string.Equals(culture.Name, "zzz", StringComparison.CurrentCultureIgnoreCase));
}

另一种方法

作为一种文化无关的替代方法,如果您不介意语言的方言,您可以构建一个包含ISO 639-2语言代码的字符串列表,并进行查找。


0
对于可能感兴趣的人,从.NET 5开始,这个重载正是做到了这一点(高效地调用Nls或Icu本地API)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接