抛出一个与捕获的相同类型的C#异常?

10
为什么(如果有的话)这个想法是不好的?
class Program
{
  static void Main(string[] args)
  {
     try
     {
        throw new NotImplementedException("Oh dear");
     }
     catch (Exception ex)
     {
        throw NewException("Whoops", ex);
     }
  }

  // This function is the salient bit here
  public static Exception NewException(String message, Exception innerException)
  {
     return Activator.CreateInstance(innerException.GetType(), message, innerException) as Exception;
  }
}

重要的一点是,该函数创建了与“innerException”相同类型的异常。
我想...“哦...发生了异常。我实际上无法在这里处理它,但我可以添加一些额外的信息并重新抛出。也许另一个调用链更高的处理程序可以处理它。”
例如,某种SQL错误。我可能无法在调用点处处理异常,但可能希望添加一些额外的“上下文”信息,例如“我正在调用此函数,并传递那个参数”。
似乎将最初引发的异常类型作为回传调用链异常的类型,而不是“Exception”或“ApplicationException”,可能是有用的。当然,我可能错了。这可能是一个非常有用的事情...但小声音建议不要这样做。
----- 编辑 -----
为了辩论起见,请考虑以下两个函数的效果(使用上面的代码):
这...经常出现的情况:
  static int SalesTotal(int customerNumber)
  {
     try
     {
        throw new DivideByZeroException(); // something you didn't expect
     }
     catch (Exception ex)
     {
        throw new ApplicationException("Unable to calculate sales for customer " + customerNumber, ex);
     }
  }

相比之下...

  static int SalesTotal(int customerNumber)
  {
     try
     {
        throw new DivideByZeroException(); // something you didn't expect
     }
     catch (Exception ex)
     {
        throw NewException("Unable to calculate sales for customer " + customerNumber, ex);
     }
  }

2
你尝试创建的异常可能没有(String, Exception)构造函数。 - Heinzi
这可能吗?如果它源自System.Exception,那怎么不可能呢? - Black Light
3
可以创建一个自定义异常类型,该类型不提供那个构造函数签名。通常情况下,子类不必提供与父类完全相同的所有构造函数。 - Bryan
Bryan,谢谢你。从技术角度来看,你的评论突显了我的示例的“不好的想法”。感谢你的贡献。 - Black Light
哎呀...这正是Heinzi之前提到的问题,但我却没注意到。我创建了足够多的自定义异常来记住所有必需的构造函数,但只是忘记了为什么需要创建它们 - 它们不会被继承。谢谢大家。 - Black Light
7个回答

12

11
请确保使用throw;而不是throw ex;重新抛出异常,否则将丢失堆栈跟踪信息。 - Heinzi
1
即使使用 throw; 也可能会出现问题:http://weblogs.asp.net/fmarguerie/archive/2008/01/02/rethrowing-exceptions-and-preserving-the-full-call-stack-trace.aspx。 - Heinzi
嗯...数据收集可能有一些可能性,但在使用上还有很大的模糊空间。 - Black Light

6
创建一个新的异常类型并不适合像这样通用的方法,因为现有代码将无法对特定错误做出反应。(但在API边界处翻译异常可能很有用。)
创建相同类型的新异常似乎很危险。你正在使用的CreateInstance重载是否会将所有字段从innerException复制到新的外部异常中?如果堆栈上面的异常处理程序依赖于解析Message属性怎么办?如果异常构造函数具有副作用呢?
在我看来,你真正要做的是记录日志,并且最好实际上进行记录和重新抛出。

一些有效的观点。我想象CreateInstance只是调用类型的构造函数,带有一个字符串和一个(内部)异常。此外,如果我发现依赖于异常消息属性格式的代码,我会更加谨慎。 - Black Light
解析异常消息绝对不是一件美好的事情,但我曾经在处理 SQL 的 Java 代码中看到过这样做,以便针对特定的约束违规做出反应。 - snemarch
其实...你提到了,我想我也在同样的情况下(但是使用C#)做过同样的事情。 - Black Light

3

我认为你应该创建自己的自定义异常类型。你将要添加什么样的信息?捕获异常的人如何知道它有额外的信息?如果使用自定义类型,他们将有额外的属性/方法来查看或调用。


我同意,只需创建一个自定义异常类型,并可能将原始异常作为新异常的 InnerException。 - digEmAll

1

完全不要捕获它,让它冒泡 - 没有重新发明轮子的必要。

你的方法不仅对于其他人来说不直观,而且还会丢失第一个异常的原始堆栈跟踪。


在原始示例中,原始堆栈并未丢失。 - Black Light
不同意,上下文至关重要。如果我能告诉用户在操作失败时他们正在做什么,那么用户通常可以自己解决问题。返回“无法将'fred'转换为整数”与“整数转换错误”的区别在于用户可以自己解决问题,而不是认为你编写了一个愚蠢的程序。 - AnthonyVO

0

在某些情况下,这是有道理的。如果您将 MyClass.MyFunction 视为程序集的公共“外部”,那么发生在其中的事情对于调用代码来说就是一个黑盒子。因此,对于调用代码而言,将其作为异常发生的点可能更合理,并且最合理的异常类型可能是与捕获的异常相同。

不过我会谨慎一些。在大多数出现这种情况的情况下,要么您应该能够预先捕获到异常并进行预防性抛出(如果您要抛出异常,越早越好),要么您抛出的异常类型并不是真正正确的类型(如果您的代码找不到所需的文件,则对您而言是 FileNotFoundException,但如果文件是实现细节,则对调用代码而言不是 FileNotFoundException),或者原始异常完全合理,应该允许其调用或使用 throw; 重新抛出。

因此,并非总是一个坏主意,但通常情况下它是一个可疑的事情。


0

在我看来,可能最好有一些自定义异常类型,其中一个层次结构用于表示“此操作失败,但系统状态就像从未尝试过一样”,另一个用于表示“操作失败,系统状态已被破坏,但可能可以撤消”,还有一个用于表示“操作失败,撤消失败;系统状态不可信。”在某些情况下,一类异常可能会被捕获并重新抛出为另一类异常(例如,如果应该完成恢复系统状态的操作失败了,即使从该操作的角度来看没有什么被破坏,无法恢复状态的失败可能会导致其被破坏;相反,如果一个操作使系统状态受到干扰,但是catch处理程序恢复了状态,它可以重新抛出为“操作失败但系统状态未受干扰”)。

我非常不喜欢尝试创建盲目匹配抛出异常类型的异常实例,因为抛出的异常可能具有处理程序期望填充的字段。我不确定为什么Microsoft将异常“几乎”设为不可变。如果异常需要是不可变的并支持克隆以传达不可变性的任何语义,那将更好。


0

只捕获你处理的异常,例如你不希望一个 SqlException 从数据层抛出到业务层(捕获它,尝试处理并抛出一个 DataLayerException 或类似的异常)。


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