如何使用类型安全的 c# .Net 资源文件实现与语言无关的错误处理?

How to implement language independent error handling using c# .Net ressource files type-safe?

问题: 我正在尝试使用 .Net 资源文件实现与语言无关的错误处理。错误消息必须以 UI 语言显示,并且可以以不同的日志语言记录。错误消息可能插入了变量的内容 (String.Format)。例如,在验证对象时可能会累积不同的错误。累积的错误将被记录并传递给 UI.

Framework: 我想使用 Resource resx 文件和 Visual Studio 资源编辑器,因为语言资源被编译成 class 允许类型-安全访问代码中的密钥。 (测试将检查每种语言是否有每个键的翻译)

实现:错误应该在错误对象中向上传递。我尝试了不同的实现,但每个都有缺点,我想知道是否有更好的解决方案或克服缺点的方法。下面的代码经过简化以表达要点。

1) 创建两条消息,UI 和错误创建时的日志消息

要创建消息,我可以使用 UI 和 LOG Culture 调用 ResourceManager 的 GetString 方法,并将这些消息保存在错误对象中。例如

    ResourceManager rm = new ResourceManager(typeof(ErrorText));
    
    if (!isValid) {
         string key = Resources.ErrorText.MaximumTitleLength
         string uiError = String.Format(GetString(key, uiCulture), MaxSeperatorLength.ToString());
         string logError = String.Format(GetString(key, logCulture), MaxSeperatorLength.ToString());
         Error err = new Error (uiError, logError);
    }

缺点:主要缺点是域class必须处理语言关注点,违反了关注点分离。代码被语言代码污染了。语言变体仅限于两种。语言决定不能推迟到以后,如果 log 和 UI 语言相同,字符串将保存两次。

2) 仅传递密钥和资源类型

    if (!isValid) {
         Error err = new Error (resType: typeof(Resources.ErrorText),
                                key: nameof(Resources.ErrorText.MaximumTitleLength), 
                                params: MaxSeperatorLength.ToString());
    }

err.GetMessage(CultureInfo) 将 return 使用所需语言的消息。

缺点:编译资源中的键class是字符串类型的属性。 C# 不允许传递对 class 属性 的引用。因此我使用 nameof() 让编译器能够检查密钥类型。在错误 class 中,密钥被保存为只读字符串,以避免意外更改。 nameof() 仅 return 键名,而不是 class 名称。因此,错误 class 需要另一个资源类型参数,如果资源文件位于不同的程序集中,还需要程序集名称。 PersonError 等特殊错误 classes 的可选工厂方法可以在 class 内部具有资源类型名称,从而使调用更简单。但是没有什么能阻止调用者使用来自不同资源密钥的密钥,因为资源类型名称和密钥是分开的。

问题:有没有更好的方法以类型安全的方式传递包含资源类型的密钥?有没有办法强制 resType 参数是资源类型?看起来创建的 class 没有可以用作选择器的接口。我想将使用过的资源 ​​classes 封装在一个自己的静态 classes 中,它保存对具体资源文件的引用,然后使用这个 class 作为 resType.[=14 的参数=]

3) 将密钥作为 lambda 传递

    if (!isValid) {
        Error err = new Error (key: () => Resources.ErrorText.MaximumTitleLength,
                               params: MaxSeperatorLength.ToString());
    }

lambda 的好处是密钥和资源类型在一个参数中传递。

缺点: 可以传递任何产生字符串的 lambda。不能强制使用资源密钥。当在 GetErrorMessage 方法中调用 lambda 时,Thread.CurrentThread.CurrentUICulture 用于决定使用哪个语言版本。所以必须保存线程文化,设置为请求的语言,然后 returned 到保存的语言。

问题:有什么方法可以限制lambda为资源键吗?我对 lambda 选项不是很熟悉。有没有办法从传递的 lambda“Resources.ErrorText.MaximumTitleLength”的右侧获取键名和资源类型,以允许通过 ResourceManager 调用 GetString(CultureInfo) 获取翻译。

一般问题:是否有更好的方法来实现多语言和类型安全错误消息的目标?

编辑: 我找到了问题 #3 的一些答案。如果我将错误 [​​=79=] 中的键视为 LambdaExpression 而不是委托,我就可以访问正文 属性。现在我可以得到(检查空值):

resource key as string -> key.Body.Member.Name
resource Type as string -> key.Body.Member.DeclaringType.FullName
assembly -> key.Body.Member.DeclaringType.Assembly

有了这些值,我可以用所需的语言阅读资源文件。

我最终使用了 LambdaExpressions。这使我可以推迟语言决定并以各种语言显示错误。

为了灵活性和减少错误文本的数量,我定义了带有占位符的常见错误文本。例如。英文资源文件中的条目如下所示:

Max_Char_Length_Exceeded -> "{0} is too long. The maximum length is {1} characters."

此外,我为错误消息创建了静态方法 class 以确保在编译时参数正确,还使用 ​​lambda 表达式进行字段名称翻译。

public static ErrorMessage MaxCharLengthExceeded(Expression<Func<string>> fieldKey, int maxLength)
{
  return new ErrorMessage(() => ErrorText.Max_Char_Length_Exceeded, fieldKey, maxLength.ToString());
}