这样做更好吗:

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

或:

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

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


当前回答

你应该总是使用"throw;"来重新抛出。net中的异常,

请参考博客文章《甩和甩前男友》。

基本上,MSIL (CIL)有两个指令——“throw”和“rethrow”,c#的“throw ex;”被编译成MSIL的“throw”和c#的“throw;”-进入MSIL“重扔”!基本上,我可以看到为什么“throw ex”覆盖堆栈跟踪的原因。

其他回答

视情况而定。在调试构建中,我希望尽可能少地查看原始堆栈跟踪。在这种情况下,“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”的一个不太可能的特例。

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

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
}

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

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

一个例子是使用另一个集合实现的专门集合。让我们假设它是一个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;
  }
}

如果抛出一个没有变量的异常(第一个例子),堆栈跟踪将包括抛出异常的原始方法。

在第二个示例中,堆栈跟踪将被更改以反映当前方法。

例子:

static string ReadAFile(string fileName) {
    string result = string.Empty;
    try {
        result = File.ReadAllLines(fileName);
    } catch(Exception ex) {
        throw;    // This will show ReadAllLines in the stack trace
        throw ex; // This will show ReadAFile in the stack trace
    }

第一个保存异常的原始堆栈跟踪,第二个用当前位置替换它。

因此,到目前为止,第一个是更好的。