Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting default CurrentCulture and CurrentUICulture (differences between .NET 4.5.2 and .NET 4.6)

Tags:

c#

.net

wpf

I have a WPF application and I would like to modify culture settings across the whole application. Here is a reduced demo that illustrates how I wanted to achieve that:

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}");
        }
    }
}

It seems to work, but not always. Here is the output (displayed in a messagebox) when I target .NET 4.5.2 and run app on Windows 7, .NET 4.5.2 on Windows 10 or .NET 4.6 on Windows 7:

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

But when I target .NET 4.6 and run app on Windows 10, I get following:

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: ,

Can somebody explain me the difference? I've checked this .NET 4.6 compatibility list, but I cannot find anything that might affect it (or I'm blind). I assume nearest related change is that CultureInfo.CurrentCulture and CultureInfo.CurrentUICulture properties are now read-write rather than read-only (source).

I'm aware that I'm setting only DefaultThreadCurrentCulture/DefaultThreadCurrentUICulture without explicitly setting CurrentCulture/CurrentUICulture as well. But according to MSDN, I suppose it's ok in my case (please correct me if I'm wrong):

If you have not explicitly set the culture of any existing threads executing in an application domain, setting the DefaultThreadCurrentCulture property also changes the culture of these threads. However, if these threads execute in another application domain, their culture is defined by the DefaultThreadCurrentCulture property in that application domain or, if no default value is defined, by the default system culture. Because of this, we recommend that you always explicitly set the culture of your main application thread, and not rely on the DefaultThreadCurrentCulture property to define the culture of the main application thread.

Also, that doesn't explain the difference in behavior between different OSs, or does it?

Interesting thing is that if I replace

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;

with

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;

the output is following:

.NET 4.5.2 on Windows 7 or .NET 4.5.2 on Windows 10:

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

.NET 4.6 on Windows 7 or .NET 4.6 on Windows 10:

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

Behavior of .NET 4.5.2 is what I expected and I think .NET 4.6 behaves wrong. Funny thing is that I would swear it was working "fine" couple of days ago (although I cannot 100% confirm it). I'm just guessing, but couldn't some Windows Update changed the behavior?

like image 890
Stalker Avatar asked Mar 30 '16 15:03

Stalker


1 Answers

Beginning in .NET 4.6, the CurrentCulture from the running thread flows through to new threads that it creates and to ThreadPool threads as they execute queued items. You can read more about mitigating this change on MSDN: Mitigation: Culture and Asynchronous Operations

Here are the options that are listed in the aforementioned document to switch back to the old behavior:

  • Explicitly set the desired CultureInfo.CurrentCulture or CultureInfo.CurrentUICulture property as the first operation in an asynchronous task

  • Call AppContext.SetSwitch("Switch.System.Globalization.NoAsyncCurrentCulture", true); at startup.

  • Override in .config

<configuration>
    <runtime>
        <AppContextSwitchOverrides value="Switch.System.Globalization.NoAsyncCurrentCulture=true" />
    </runtime>
</configuration>
like image 179
Fls'Zen Avatar answered Oct 13 '22 20:10

Fls'Zen