C# 这是临时更改当前线程的好方法吗';中国文化?

C# 这是临时更改当前线程的好方法吗';中国文化?,c#,.net,localization,cultureinfo,C#,.net,Localization,Cultureinfo,我在一个相当大的ASP.NETWebForms应用程序上工作,该应用程序目前主要在美国使用。我们正在将其推广到世界其他地区,这当然意味着我们目前正在致力于将应用程序的所有领域本地化。一般来说,我们的方法是在每个请求开始时设置当前线程的CurrentCulture和CurrentUICulture属性,以支持基于当前用户的区域设置的正确格式和资源提取 但是,在某些情况下,我们需要使用当前用户的区域性以外的区域性运行特定的代码位。例如,“用户A”居住在德国,但为一家与法国其他公司开展业务的公司工作。

我在一个相当大的ASP.NETWebForms应用程序上工作,该应用程序目前主要在美国使用。我们正在将其推广到世界其他地区,这当然意味着我们目前正在致力于将应用程序的所有领域本地化。一般来说,我们的方法是在每个请求开始时设置当前线程的CurrentCulture和CurrentUICulture属性,以支持基于当前用户的区域设置的正确格式和资源提取

但是,在某些情况下,我们需要使用当前用户的区域性以外的区域性运行特定的代码位。例如,“用户A”居住在德国,但为一家与法国其他公司开展业务的公司工作。当“用户A”希望为其中一家法国公司创建发票(PDF)时,我们希望发票生成代码使用“fr fr”区域性而不是“de de”区域性运行

我已经考虑了几种简单的方法,我想知道我是否正确地进行了这项工作。我主要关心的是性能和线程安全

一种方法涉及一个静态方法,该方法旨在使用提供的区域性运行给定的任务。大概是这样的:

 public static void RunWithCulture(CultureInfo culture, Action task)
    {
        if (culture == null)
            throw new ArgumentNullException("culture");

        var originalCulture = new
                                  {
                                      Culture = Thread.CurrentThread.CurrentCulture,
                                      UICulture = Thread.CurrentThread.CurrentUICulture
                                  };

        try
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
            task();
        }
        finally
        {
            Thread.CurrentThread.CurrentCulture = originalCulture.Culture;
            Thread.CurrentThread.CurrentUICulture = originalCulture.UICulture;
        }
    }
var customerCulture = new CultureInfo(currentCustomer.Locale);
CultureRunner.RunWithCulture(customerCulture, () => invoiceService.CreateInvoice(currentCustomer.CustomerId));
var customerCulture = new CultureInfo(currentCustomer.Locale);
using(new CultureRunner(currentCustomer.Locale))
{
  invoiceService.CreateInvoice(currentCustomer.CustomerId);
}
public static class CultureInfoExtensions {
  public static IDisposable AsCurrent(this CultureInfo culture) {
    return new CultureRunner(culture);
  }
}
然后可以如下方式调用此方法:

 public static void RunWithCulture(CultureInfo culture, Action task)
    {
        if (culture == null)
            throw new ArgumentNullException("culture");

        var originalCulture = new
                                  {
                                      Culture = Thread.CurrentThread.CurrentCulture,
                                      UICulture = Thread.CurrentThread.CurrentUICulture
                                  };

        try
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
            task();
        }
        finally
        {
            Thread.CurrentThread.CurrentCulture = originalCulture.Culture;
            Thread.CurrentThread.CurrentUICulture = originalCulture.UICulture;
        }
    }
var customerCulture = new CultureInfo(currentCustomer.Locale);
CultureRunner.RunWithCulture(customerCulture, () => invoiceService.CreateInvoice(currentCustomer.CustomerId));
var customerCulture = new CultureInfo(currentCustomer.Locale);
using(new CultureRunner(currentCustomer.Locale))
{
  invoiceService.CreateInvoice(currentCustomer.CustomerId);
}
public static class CultureInfoExtensions {
  public static IDisposable AsCurrent(this CultureInfo culture) {
    return new CultureRunner(culture);
  }
}
我还考虑过创建一个实现IDisposable的类,该类将负责在其ctor中设置线程区域性,然后在Dispose方法中返回原始区域性,因此您可以这样调用它:

 public static void RunWithCulture(CultureInfo culture, Action task)
    {
        if (culture == null)
            throw new ArgumentNullException("culture");

        var originalCulture = new
                                  {
                                      Culture = Thread.CurrentThread.CurrentCulture,
                                      UICulture = Thread.CurrentThread.CurrentUICulture
                                  };

        try
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
            task();
        }
        finally
        {
            Thread.CurrentThread.CurrentCulture = originalCulture.Culture;
            Thread.CurrentThread.CurrentUICulture = originalCulture.UICulture;
        }
    }
var customerCulture = new CultureInfo(currentCustomer.Locale);
CultureRunner.RunWithCulture(customerCulture, () => invoiceService.CreateInvoice(currentCustomer.CustomerId));
var customerCulture = new CultureInfo(currentCustomer.Locale);
using(new CultureRunner(currentCustomer.Locale))
{
  invoiceService.CreateInvoice(currentCustomer.CustomerId);
}
public static class CultureInfoExtensions {
  public static IDisposable AsCurrent(this CultureInfo culture) {
    return new CultureRunner(culture);
  }
}

我这样做完全错了吗?如果这些方法中有哪一种更可取?

我喜欢使用
方法。我还将创建一个扩展方法,以提高阅读效果:

var customerCulture = new CultureInfo(currentCustomer.Locale);  
using (customerCulture.AsCurrent()) {
  invoiceService.CreateInvoice(currentCustomer.CustomerId);
}
大概是这样的:

 public static void RunWithCulture(CultureInfo culture, Action task)
    {
        if (culture == null)
            throw new ArgumentNullException("culture");

        var originalCulture = new
                                  {
                                      Culture = Thread.CurrentThread.CurrentCulture,
                                      UICulture = Thread.CurrentThread.CurrentUICulture
                                  };

        try
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
            task();
        }
        finally
        {
            Thread.CurrentThread.CurrentCulture = originalCulture.Culture;
            Thread.CurrentThread.CurrentUICulture = originalCulture.UICulture;
        }
    }
var customerCulture = new CultureInfo(currentCustomer.Locale);
CultureRunner.RunWithCulture(customerCulture, () => invoiceService.CreateInvoice(currentCustomer.CustomerId));
var customerCulture = new CultureInfo(currentCustomer.Locale);
using(new CultureRunner(currentCustomer.Locale))
{
  invoiceService.CreateInvoice(currentCustomer.CustomerId);
}
public static class CultureInfoExtensions {
  public static IDisposable AsCurrent(this CultureInfo culture) {
    return new CultureRunner(culture);
  }
}
CultureRunner
示例:

public class CultureRunner : IDisposable
{
    readonly CultureInfo originalCulture;
    readonly CultureInfo originalUICulture;

    public CultureRunner(CultureInfo culture)
    {
        if (culture == null)
            throw new ArgumentNullException(nameof(culture));

        originalCulture = Thread.CurrentThread.CurrentCulture;
        originalUICulture = Thread.CurrentThread.CurrentUICulture;

        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = culture;
    }

    public void Dispose()
    {
        Thread.CurrentThread.CurrentCulture = originalCulture;
        Thread.CurrentThread.CurrentUICulture = originalUICulture;
    }
}
或者,如果总是由您的客户对象设置区域性,那么另一种扩展方法将进一步提高抽象性:

using (currentCustomer.CultureContext()) {
  invoiceService.CreateInvoice(currentCustomer.CustomerId);
}

既然你在问暂时改变当前线程的文化是否是个好主意,我只能回答:。如果并且只有在没有其他方法让事情运转的情况下,才可以使用它。这只是因为这种切换容易出错。好吧,你不会忘记用Jordão(尊敬)给你的代码把事情改回去,但是…
目前,您的客户希望创建法国发票。我假设您希望使用法语日期、数字和货币格式。没关系。但是如果将来某个将来需要以其他格式打印出来,例如,这是源于德语的格式,该怎么办?你打算创造一些丑陋的作品吗

我知道它可能超出您的控制范围(比如报告软件可能是第三方独立解决方案,您无法控制它如何处理
ToString()
),但如果它在您的控制范围内,我建议首先以正确的格式提供数据。例如,您可以创建一些数据转换层(DTO)并正确格式化数据(通过
ToString(IFormatProvider)
)。我知道这是一个相当大的努力,但既然你问的是正确的做事方式


如果我们在同一个组织中,我会进行I18n代码审查,您可以肯定,我会指出暂时性的文化变化是一种缺陷。通常有一种方法可以避免这种情况。

使用方法对我来说也是最好的。谢谢您的回复。当你说“…这种切换容易出错”时,你的意思是它会导致难以维护的代码,还是这种方法会导致应用程序中的实际运行时错误?此外,我没有选择使用ToString(IFormatProvider)方法的原因之一是我需要的不仅仅是正确的日期和数字格式;我还需要从resx文件中提取一些正确语言的静态文本。除了更改当前线程上的CurretUICulture属性之外,我还没有找到一种有效且安全的方法来使用当前语言拉入消息。它不应该在应用程序的其他部分导致错误,至少在您记得将其切换回时是这样。至于从resx文件中读取,您可以始终实例化ResourceManager,并将GetString()与适当的CultureInfo一起使用。您可以添加更通用的示例,因为CultureRunner未声明。我需要使用不同的区域性运行一个方法。你能更新你的答案吗?