寻求ResXFileCodeGenerator的线程安全本地化替代方案
解决ResXFileCodeGenerator静态Culture多线程冲突问题,保留强类型优势
ResXFileCodeGenerator配合.resx资源文件的本地化方案确实好用:可视化编辑资源、自动生成带智能提示的强类型属性,切换语言只需设置静态Culture属性就能流畅切换:
MyStrings.Culture = new CultureInfo("ru"); Console.Write(MyStrings.MyTranslatedString); // 俄语输出 MyStrings.Culture = CultureInfo.InvariantCulture; Console.Write(MyStrings.MyTranslatedString); // 英语输出
但静态Culture属性是全局共享的,多线程场景下很容易出现语言串用的问题。直接调用ResourceManager.GetString虽然能指定Culture,但丢了强类型的便捷性——改个资源键名就得手动搜遍代码改字符串,太麻烦。
这里给个两全其美的方案:基于自动生成的静态资源类,包装一个带实例级Culture的本地化类,既保留.resx的编辑便捷性和智能提示,又支持多线程安全。
实现步骤
- 保留原.resx文件的
ResXFileCodeGenerator设置,让它继续生成静态资源类(比如MyStrings)。 - 创建一个本地化包装类,内部持有独立的
CultureInfo,通过原静态类的ResourceManager获取资源,同时用强类型属性暴露资源:
public class MyStringsLocalizer { private readonly CultureInfo _culture; public MyStringsLocalizer(CultureInfo culture) { _culture = culture ?? CultureInfo.InvariantCulture; } // 对应静态类里的每个资源属性,自动提示全保留 public string MyTranslatedString => MyStrings.ResourceManager.GetString(nameof(MyTranslatedString), _culture); public string CommandsDescription => MyStrings.ResourceManager.GetString(nameof(CommandsDescription), _culture); }
使用示例
每个线程可以创建自己的本地化实例,完全不会互相干扰:
// 线程1使用俄语 var ruLocalizer = new MyStringsLocalizer(new CultureInfo("ru")); Console.WriteLine(ruLocalizer.MyTranslatedString); // 线程2使用英语 var enLocalizer = new MyStringsLocalizer(CultureInfo.InvariantCulture); Console.WriteLine(enLocalizer.MyTranslatedString);
优化:自动生成包装类属性
如果资源很多,手动写每个属性太麻烦,可以用T4模板自动生成包装类的属性,每次.resx更新后重新生成模板即可:
<#@ template language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Reflection" #> <#@ import namespace="System" #> public class MyStringsLocalizer { private readonly CultureInfo _culture; public MyStringsLocalizer(CultureInfo culture) { _culture = culture ?? CultureInfo.InvariantCulture; } <# // 自动遍历静态资源类的所有字符串属性 var resourceType = typeof(MyStrings); var properties = resourceType.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); foreach (var prop in properties) { if (prop.PropertyType == typeof(string)) { #> public string <#= prop.Name #> => MyStrings.ResourceManager.GetString(nameof(<#= prop.Name #>), _culture); <# } } #> }
这样既保留了原方案的所有便捷性,又完美解决了多线程下的静态Culture冲突问题。
内容的提问来源于stack exchange,提问作者Vitaly Filatenko
相关产品推荐
相关产品推荐

