这样做更好吗:

try
{
    ...
}
catch (Exception ex)
{
    ...
    throw;
}

或:

try
{
    ...
}
catch (Exception ex)
{
    ...
    throw ex;
}

他们做同样的事情吗?一个比另一个好吗?


当前回答

我同意,大多数情况下,您要么想要进行普通抛出,以尽可能多地保存有关错误的信息,要么想要抛出一个新的异常,其中可能包含一个内部异常,或者不抛出,这取决于您想要知道导致错误的内部事件的可能性有多大。

但也有例外。在几种情况下,一个方法会调用另一个方法,而在内部调用中导致异常的条件应该被认为是外部调用中的相同异常。

一个例子是使用另一个集合实现的专门集合。让我们假设它是一个DistinctList<T>,它包装了一个List<T>,但拒绝重复项。

如果有人调用ICollection<T>。在集合类上的CopyTo,它可能只是直接调用内部集合上的CopyTo(如果所有自定义逻辑只应用于向集合添加或设置它)。现在,调用抛出的条件与集合抛出的条件完全相同,以匹配ICollection<T>. copyto的文档。

现在,您完全可以不捕获异常,让它通过。但是在这里,当用户调用DistinctList<T>上的东西时,从List<T>得到一个异常。这并不是世界末日,但是您可能希望隐藏那些实现细节。

或者你也可以自己检查:

public CopyTo(T[] array, int arrayIndex)
{
  if(array == null)
    throw new ArgumentNullException("array");
  if(arrayIndex < 0)
    throw new ArgumentOutOfRangeException("arrayIndex", "Array Index must be zero or greater.");
  if(Count > array.Length + arrayIndex)
    throw new ArgumentException("Not enough room in array to copy elements starting at index given.");
  _innerList.CopyTo(array, arrayIndex);
}

这并不是最糟糕的代码,因为它是样板代码,我们可以从CopyTo的其他实现中复制它,而不是简单的传递,我们必须自己实现它。不过,它不必要地重复将在_innerList中执行的完全相同的检查。CopyTo(array, arrayIndex),所以它唯一添加到代码中的东西是6行,那里可能有一个错误。

我们可以检查并括起来:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentNullException ane)
  {
    throw new ArgumentNullException("array", ane);
  }
  catch(ArgumentOutOfRangeException aore)
  {
    throw new ArgumentOutOfRangeException("Array Index must be zero or greater.", aore);
  }
  catch(ArgumentException ae)
  {
    throw new ArgumentException("Not enough room in array to copy elements starting at index given.", ae);
  }
}

就新添加的代码而言,可能会有bug,这就更糟糕了。我们从内部异常中得不到任何东西。如果我们传递一个空数组给这个方法,并接收到一个ArgumentNullException异常,我们不会通过检查内部异常和调用_innerList来学习任何东西。CopyTo被传递一个空数组并抛出一个ArgumentNullException。

在这里,我们可以做任何我们想做的事情:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentException ae)
  {
    throw ae;
  }
}

如果用户使用不正确的参数调用它,我们期望抛出的每一个异常,都将由该重新抛出正确抛出。如果在这里使用的逻辑中有错误,那么它是在两行中的其中一行中——要么是我们错误地决定了这种方法可以在这种情况下工作,要么是我们错误地将ArgumentException作为异常类型来查找。这是catch块可能仅有的两个bug。

Now. I still agree that most of the time you either want a plain throw; or you want to construct your own exception to more directly match the problem from the perspective of the method in question. There are cases like the above where rethrowing like this makes more sense, and there are plenty of other cases. E.g., to take a very different example, if an ATOM file reader implemented with a FileStream and an XmlTextReader receives a file error or invalid XML, then it will perhaps want to throw exactly the same exception it received from those classes, but it should look to the caller that it is AtomFileReader that is throwing a FileNotFoundException or XmlException, so they might be candidates for similarly rethrowing.

我们也可以将两者结合起来:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentException ae)
  {
    throw ae;
  }
  catch(Exception ex)
  {
    //we weren't expecting this, there must be a bug in our code that put
    //us into an invalid state, and subsequently let this exception happen.
    LogException(ex);
    throw;
  }
}

其他回答

您应该始终使用以下语法来重新抛出异常。否则你将停止堆栈跟踪:

throw;

如果打印由throw ex产生的跟踪,您将看到它结束于该语句,而不是异常的真正来源。

基本上,使用抛掷前女友应该被视为刑事犯罪。


如果需要重新抛出来自其他地方(AggregateException, TargetInvocationException)或另一个线程的异常,也不应该直接重新抛出。而是使用ExceptionDispatchInfo保存所有必要的信息。

try
{
    methodInfo.Invoke(...);
}
catch (System.Reflection.TargetInvocationException e)
{
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e.InnerException).Throw();
    throw; // just to inform the compiler that the flow never leaves the block
}

视情况而定。在调试构建中,我希望尽可能少地查看原始堆栈跟踪。在这种情况下,“throw;”符合要求。

然而,在发布版本中,(a)我想记录包含原始堆栈跟踪的错误,一旦完成,(b)重新设计错误处理以使用户更有意义。这里“抛出异常”是有意义的。重新抛出错误确实会丢弃原始的堆栈跟踪,但非开发人员从查看堆栈跟踪信息中得不到任何信息,因此可以重新抛出错误。

        void TrySuspectMethod()
        {
            try
            {
                SuspectMethod();
            }
#if DEBUG
            catch
            {
                //Don't log error, let developer see
                //original stack trace easily
                throw;
#else
            catch (Exception ex)
            {
                //Log error for developers and then
                //throw a error with a user-oriented message
                throw new Exception(String.Format
                    ("Dear user, sorry but: {0}", ex.Message));
#endif
            }
        }

问题的措辞,让"Throw:"和。“Throw ex;”让它有点转移注意力。真正的选择是在“Throw;”和“Throw Exception”之间,其中“Throw ex;”是“Throw Exception”的一个不太可能的特例。

我的偏好是使用

try
{
}
catch (Exception ex)
{
     ...
     throw new Exception ("Add more context here", ex)
}

这保留了原来的错误,但是它允许您添加更多的上下文,例如对象ID、连接字符串等等。我的异常报告工具通常有五个链接的异常要报告,每个报告更详细。

第一个比较好。如果尝试调试第二个异常并查看调用堆栈,则无法看到原始异常来自何处。如果真的需要重新抛出,有一些技巧可以保持调用堆栈的完整性(尝试搜索,它之前已经被回答过了)。

我同意,大多数情况下,您要么想要进行普通抛出,以尽可能多地保存有关错误的信息,要么想要抛出一个新的异常,其中可能包含一个内部异常,或者不抛出,这取决于您想要知道导致错误的内部事件的可能性有多大。

但也有例外。在几种情况下,一个方法会调用另一个方法,而在内部调用中导致异常的条件应该被认为是外部调用中的相同异常。

一个例子是使用另一个集合实现的专门集合。让我们假设它是一个DistinctList<T>,它包装了一个List<T>,但拒绝重复项。

如果有人调用ICollection<T>。在集合类上的CopyTo,它可能只是直接调用内部集合上的CopyTo(如果所有自定义逻辑只应用于向集合添加或设置它)。现在,调用抛出的条件与集合抛出的条件完全相同,以匹配ICollection<T>. copyto的文档。

现在,您完全可以不捕获异常,让它通过。但是在这里,当用户调用DistinctList<T>上的东西时,从List<T>得到一个异常。这并不是世界末日,但是您可能希望隐藏那些实现细节。

或者你也可以自己检查:

public CopyTo(T[] array, int arrayIndex)
{
  if(array == null)
    throw new ArgumentNullException("array");
  if(arrayIndex < 0)
    throw new ArgumentOutOfRangeException("arrayIndex", "Array Index must be zero or greater.");
  if(Count > array.Length + arrayIndex)
    throw new ArgumentException("Not enough room in array to copy elements starting at index given.");
  _innerList.CopyTo(array, arrayIndex);
}

这并不是最糟糕的代码,因为它是样板代码,我们可以从CopyTo的其他实现中复制它,而不是简单的传递,我们必须自己实现它。不过,它不必要地重复将在_innerList中执行的完全相同的检查。CopyTo(array, arrayIndex),所以它唯一添加到代码中的东西是6行,那里可能有一个错误。

我们可以检查并括起来:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentNullException ane)
  {
    throw new ArgumentNullException("array", ane);
  }
  catch(ArgumentOutOfRangeException aore)
  {
    throw new ArgumentOutOfRangeException("Array Index must be zero or greater.", aore);
  }
  catch(ArgumentException ae)
  {
    throw new ArgumentException("Not enough room in array to copy elements starting at index given.", ae);
  }
}

就新添加的代码而言,可能会有bug,这就更糟糕了。我们从内部异常中得不到任何东西。如果我们传递一个空数组给这个方法,并接收到一个ArgumentNullException异常,我们不会通过检查内部异常和调用_innerList来学习任何东西。CopyTo被传递一个空数组并抛出一个ArgumentNullException。

在这里,我们可以做任何我们想做的事情:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentException ae)
  {
    throw ae;
  }
}

如果用户使用不正确的参数调用它,我们期望抛出的每一个异常,都将由该重新抛出正确抛出。如果在这里使用的逻辑中有错误,那么它是在两行中的其中一行中——要么是我们错误地决定了这种方法可以在这种情况下工作,要么是我们错误地将ArgumentException作为异常类型来查找。这是catch块可能仅有的两个bug。

Now. I still agree that most of the time you either want a plain throw; or you want to construct your own exception to more directly match the problem from the perspective of the method in question. There are cases like the above where rethrowing like this makes more sense, and there are plenty of other cases. E.g., to take a very different example, if an ATOM file reader implemented with a FileStream and an XmlTextReader receives a file error or invalid XML, then it will perhaps want to throw exactly the same exception it received from those classes, but it should look to the caller that it is AtomFileReader that is throwing a FileNotFoundException or XmlException, so they might be candidates for similarly rethrowing.

我们也可以将两者结合起来:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentException ae)
  {
    throw ae;
  }
  catch(Exception ex)
  {
    //we weren't expecting this, there must be a bug in our code that put
    //us into an invalid state, and subsequently let this exception happen.
    LogException(ex);
    throw;
  }
}