设置默认的CurrentCulture和CurrentUICulture (.NET 4.5.2和.NET 4.6之间的差异)

6

我有一个WPF应用程序,我想在整个应用程序中修改文化设置。这是一个简化的演示,说明了我想要实现的方式:

using System.Windows;

namespace CultureProblem3
{
    public partial class MainWindow : Window
    {
        private System.Text.StringBuilder _sb;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void btn_Action_Click(object sender, RoutedEventArgs e)
        {
            var ci = new System.Globalization.CultureInfo(System.Globalization.CultureInfo.CurrentCulture.Name);
            ci.NumberFormat.NumberGroupSeparator = "xxx";
            System.Globalization.CultureInfo.DefaultThreadCurrentCulture = ci;
            System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = ci;

            _sb = new System.Text.StringBuilder();

            WriteInfo(_sb, "DefaultThreadCurrentCulture", System.Globalization.CultureInfo.DefaultThreadCurrentCulture);
            WriteInfo(_sb, "DefaultThreadCurrentUICulture", System.Globalization.CultureInfo.DefaultThreadCurrentUICulture);
            WriteInfo(_sb, "CultureInfo.CurrentCulture", System.Globalization.CultureInfo.CurrentCulture);
            WriteInfo(_sb, "CultureInfo.CurrentUICulture", System.Globalization.CultureInfo.CurrentUICulture);
            WriteInfo(_sb, "CurrentThread.CurrentCulture", System.Threading.Thread.CurrentThread.CurrentCulture);
            WriteInfo(_sb, "CurrentThread.CurrentUICulture", System.Threading.Thread.CurrentThread.CurrentUICulture);

            var t = new System.Threading.Thread(DoWork);
            t.Start();
            System.Threading.Thread.Sleep(1000);

            MessageBox.Show(_sb.ToString());
        }

        private void DoWork()
        {
            WriteInfo(_sb, "CultureInfo.CurrentCulture - another thread", System.Globalization.CultureInfo.CurrentCulture);
            WriteInfo(_sb, "CultureInfo.CurrentUICulture - another thread", System.Globalization.CultureInfo.CurrentUICulture);
            WriteInfo(_sb, "CurrentThread.CurrentCulture - another thread", System.Threading.Thread.CurrentThread.CurrentCulture);
            WriteInfo(_sb, "CurrentThread.CurrentUICulture - another thread", System.Threading.Thread.CurrentThread.CurrentUICulture);
        }

        private void WriteInfo(System.Text.StringBuilder sb, string desc, System.Globalization.CultureInfo ci)
        {
            sb.AppendLine($"{desc}: {ci.NumberFormat.NumberGroupSeparator}");
        }
    }
}

看起来它是可以工作的,但并不总是如此。当我在Windows 7上针对.NET 4.5.2运行应用程序或在Windows 10上使用.NET 4.5.2或.NET 4.6时,以下是输出结果(显示在消息框中):

DefaultThreadCurrentCulture: xxx
DefaultThreadCurrentUICulture: xxx
CultureInfo.CurrentCulture: xxx
CultureInfo.CurrentUICulture: xxx
CurrentThread.CurrentCulture: xxx
CurrentThread.CurrentUICulture: xxx
CultureInfo.CurrentCulture - another thread: xxx
CultureInfo.CurrentUICulture - another thread: xxx
CurrentThread.CurrentCulture - another thread: xxx
CurrentThread.CurrentUICulture - another thread: xxx

但是当我以.NET 4.6为目标,在Windows 10上运行应用程序时,会出现以下情况:

DefaultThreadCurrentCulture: xxx
DefaultThreadCurrentUICulture: xxx
CultureInfo.CurrentCulture: ,
CultureInfo.CurrentUICulture: ,
CurrentThread.CurrentCulture: ,
CurrentThread.CurrentUICulture: ,
CultureInfo.CurrentCulture - another thread: ,
CultureInfo.CurrentUICulture - another thread: ,
CurrentThread.CurrentCulture - another thread: ,
CurrentThread.CurrentUICulture - another thread: ,

有人能解释一下它们之间的区别吗?我已经查看了.NET 4.6兼容性列表,但我没有找到任何可能影响它的东西(或者我视力不好)。我认为最相关的变化是CultureInfo.CurrentCulture和CultureInfo.CurrentUICulture属性现在是可读写的,而不是只读的(来源)。
我知道我只设置了DefaultThreadCurrentCulture/DefaultThreadCurrentUICulture,而没有明确设置CurrentCulture/CurrentUICulture。但是根据MSDN的说法,我认为在我的情况下没问题(如果我错了,请纠正我)。
如果您没有显式设置应用程序域中正在执行的任何现有线程的区域性,则设置DefaultThreadCurrentCulture属性也会更改这些线程的区域性。但是,如果这些线程在另一个应用程序域中执行,则它们的区域性由该应用程序域中的DefaultThreadCurrentCulture属性定义,或者如果未定义默认值,则由默认系统区域性定义。因此,我们建议您始终明确设置主应用程序线程的区域性,并且不依赖于DefaultThreadCurrentCulture属性来定义主应用程序线程的区域性。

此外,这并不能解释不同操作系统之间行为的差异,是吗?

有趣的是,如果我替换

var ci = new System.Globalization.CultureInfo(System.Globalization.CultureInfo.CurrentCulture.Name);
ci.NumberFormat.NumberGroupSeparator = "xxx";
System.Globalization.CultureInfo.DefaultThreadCurrentCulture = ci;
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = ci;

带有

var ci = new System.Globalization.CultureInfo(System.Globalization.CultureInfo.CurrentCulture.Name);
ci.NumberFormat.NumberGroupSeparator = "xxx";
System.Globalization.CultureInfo.DefaultThreadCurrentCulture = ci;
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = ci;

var ci2 = new System.Globalization.CultureInfo(System.Globalization.CultureInfo.CurrentCulture.Name);
ci2.NumberFormat.NumberGroupSeparator = "yyy";
System.Threading.Thread.CurrentThread.CurrentCulture = ci2;
System.Threading.Thread.CurrentThread.CurrentUICulture = ci2;

输出如下:

在 Windows 7 上使用 .NET 4.5.2 或在 Windows 10 上使用 .NET 4.5.2:

DefaultThreadCurrentCulture: xxx
DefaultThreadCurrentUICulture: xxx
CultureInfo.CurrentCulture: yyy
CultureInfo.CurrentUICulture: yyy
CurrentThread.CurrentCulture: yyy
CurrentThread.CurrentUICulture: yyy
CultureInfo.CurrentCulture - another thread: xxx
CultureInfo.CurrentUICulture - another thread: xxx
CurrentThread.CurrentCulture - another thread: xxx
CurrentThread.CurrentUICulture - another thread: xxx

在Windows 7上的.NET 4.6或Windows 10上的.NET 4.6:

DefaultThreadCurrentCulture: xxx
DefaultThreadCurrentUICulture: xxx
CultureInfo.CurrentCulture: yyy
CultureInfo.CurrentUICulture: yyy
CurrentThread.CurrentCulture: yyy
CurrentThread.CurrentUICulture: yyy
CultureInfo.CurrentCulture - another thread: yyy
CultureInfo.CurrentUICulture - another thread: yyy
CurrentThread.CurrentCulture - another thread: yyy
CurrentThread.CurrentUICulture - another thread: yyy

.NET 4.5.2的行为符合我的预期,我认为.NET 4.6的行为是错误的。有趣的是,几天前我可以发誓它“正常”工作(尽管我不能100%确认)。我只是猜测,但是一些Windows更新可能会改变其行为?


1
请查看 CultureInfo.CurrentUICulture 的备注 - 有关于 .Net 4.6 的一些不同之处的说明。这可能与此相关吗? - Matthew Watson
如果您尝试在窗口显示之前设置文化,会发生什么情况呢?此外,您没有覆盖LanguageProperty。 - Sinatr
2
很高兴看到他们解决了这个问题,.NET 4.5的DefaultThreadCurrent/UI/Culture是在.NET中这个古老问题上的一个相当粗糙的临时解决方案。现在,Culture在执行上下文中正确地流动,就像它应该的那样,确保异步/等待代码特别是使用正确的文化运行。恢复旧行为最快的方法是将框架目标修改回来。这类错误修复对所选目标非常敏感,无论实际安装的.NET版本如何。 - Hans Passant
@MatthewWatson:我看到了这个注释,但是我不认为它适用于我的情况,因为我没有使用线程池中的线程,也没有使用基于任务的异步操作。所以我认为它不应该从调用线程继承文化,而应该使用DefaultThreadCurrentCulture/DefaultThreadCurrentUICulture。如果我错了,请纠正我。 - Stalker
@Sinatr:之前设置文化并没有改变情况。最初我将其放置在应用程序启动中。此外,我认为覆盖语言属性也无济于事。我的CultureInfo具有相同的语言,只是一些格式设置不同。 - Stalker
1个回答

6
从.NET 4.6开始,运行线程的CurrentCulture会流动到它创建的新线程和执行排队项的线程池线程中。您可以在MSDN上阅读有关缓解此更改的更多信息:缓解:文化和异步操作 以下是在上述文档中列出以切换回旧行为的选项:
  • 在异步任务中显式设置所需的CultureInfo.CurrentCulture或CultureInfo.CurrentUICulture属性作为第一个操作

  • 在启动时调用AppContext.SetSwitch("Switch.System.Globalization.NoAsyncCurrentCulture", true);

  • 在.config中覆盖

<configuration>
    <runtime>
        <AppContextSwitchOverrides value="Switch.System.Globalization.NoAsyncCurrentCulture=true" />
    </runtime>
</configuration>


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