异常构造函数重载

3

在C#中创建自定义Exception类型时,为什么认为重载所有这些构造函数是一个好的实践:

Exception()
Exception(String)
Exception(String, Exception)

请见:https://learn.microsoft.com/zh-cn/dotnet/standard/exceptions/best-practices-for-exceptions#include-three-constructors-in-custom-exception-classes 在这些建议中,我没有看到提供任何理由。
如果我想创建一个自定义的Exception,用于在调用其他系统时出现未知错误时抛出,为什么认为在此处重写其他构造函数是一种好方法,如果我只需要有关失败系统和导致失败的操作的信息呢?
public class ExternalSystemCallException : Exception {
    public string FailedSystem { get; }
    public string OperationName { get; }

    public ExternalSystemCallFailedException(string failedSystem, string operationName) {
        FailedSystem = failedSystem;
        OperationName = operationName;
    }
}

声明:我知道可能还有其他信息可以在此传递,因此这只是一个相当简单的示例。

更新 1:

所以我理解的是,我应该覆盖所有构造函数,但还要额外添加异常所需的所有参数。这正确吗?

例如:

Exception(string failedSystem, string operationName) 
    : base()

Exception(string failedSystem, string operationName, string message) 
    : base(message)

Exception(string failedSystem, string operationName, string message, Exception innerException) 
    : base(message, innerException)

5
因为想要抛出异常的开发人员期望你的类有它们并且能够正常处理。Exception.Message 必须 包含解释出错情况的消息。很多时候,这是在SO问题中记录或报告的唯一信息。然而,你的类却会返回一个空字符串。异常可以被嵌套,实际上自定义异常通常会包装其他异常。你的 ExternalSystemCallException 很可能是响应某个其他异常而引发的。用户、支持人员和在未来都想知道那个异常是什么。 - Panagiotis Kanavos
最佳实践描述了一种模式的概括,以便其他开发人员和系统内部可以重用。你的例子描述了一个特定的用例,因此不适合这个实践。最佳实践适用于以下场景:1. 没有信息的默认抛出。2. 具有自定义消息的抛出。3. 具有内部异常和自定义消息的抛出。 - Spook Kruger
Exception() - 发生异常,Exception(String) - 带有消息的异常,Exception(String, Exception) - 带有消息内部原因的异常。 - Dmitry Bychenko
1
顺便提一下,除非你覆盖 ToString() 方法,否则 FailedSystemOperationName 不会出现在日志或消息中。格式化字符串和对 ToString() 的调用最终将调用 Exception.ToString(),该方法返回 Message、所有内部异常和调用堆栈。如果你想查看这些属性,就需要在构造函数中设置消息。 - Panagiotis Kanavos
1个回答

6
因为Exception类中的MessageInnerException都是只读属性,这意味着它们唯一可以设置的方式是通过构造函数。你的实现没有包括相关的构造函数,因此无法设置这些属性。这意味着你的异常会出现以下问题:
  1. 丢失与其相关的其他异常中包含的有价值信息(如果有的话)。它的InnerException始终为空。

  2. 在记录或向用户显示时,没有任何可读的信息。在记录异常时,只会考虑基类Exception的属性,如MessageInnerException。对于你的异常,这些属性始终为空,因此日志中只会显示堆栈跟踪(即使那也不完整),你的FailedSystemOperationName也不会显示在日志中。

你可能认为代码使用者会捕获特定的ExternalSystemCallException异常,然后根据其属性采取行动,但这并不总是发生。你的代码可能作为较大操作的一部分而使用,然后整个堆栈中将会有一些捕获所有异常的处理程序,它只会记录异常并向用户显示一些错误消息。因此,设置基类Exception属性的有意义的值非常重要。

要"修复"你的异常类型,你可以考虑像这样做:

public class ExternalSystemCallException : Exception
{
    public string FailedSystem { get; }
    public string OperationName { get; }

    public ExternalSystemCallException(
             string failedSystem, 
             string operationName, 
             Exception innerException = null)
            : base($"Operation {operationName} failed in {failedSystem}", innerException) {
        FailedSystem = failedSystem;
        OperationName = operationName;
    }        
}

这样你就可以始终设置Message为有意义的值,并允许在需要时传递InnerException。如果没有提供系统和操作名称值,可以省略空的Exception构造函数,以避免抛出不合适的异常。


考虑日志记录:是否有机制将“FailedSystem”和“OperationName”属性一并记录在日志中? - Sebastian Krogull
@SebastianKrogull 我已经更新了答案并提出建议。 - Evk
1
@SebastianKrogull 在你的情况下,你可以从传递给构造函数的信息中自己构建消息(就像这个答案中的示例一样),因此没有理由也接受“消息”参数。 - Evk

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