在C#中重新抛出异常的正确方法是什么?

时间:2008-10-07 13:33:55

标签: c# .net exception-handling

我有一个问题,源于我的伴侣以与我不同的方式做事。

这样做会更好:

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

或者这个:

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

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

9 个答案:

答案 0 :(得分:738)

您应该始终使用以下语法来重新抛出异常,否则您将踩踏堆栈跟踪:

throw;

如果您打印“throw ex”产生的跟踪,您将看到它在该语句上结束,而不是在异常的真实来源。

基本上,使用“throw ex”应被视为刑事犯罪。

答案 1 :(得分:151)

我的偏好是使用

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

这会保留原始错误,但允许您放置更多上下文,例如对象ID,连接字符串等。通常,我的异常报告工具将有5个链式异常报告,每个异常报告更多详细信息。

答案 2 :(得分:35)

如果你使用 out 一个变量抛出异常(第二个例子),StackTrace将包含抛出异常的原始方法。

在第一个示例中,将更改StackTrace以反映当前方法。

示例:

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

答案 3 :(得分:22)

第一个保留异常的原始堆栈跟踪,第二个将其替换为当前位置。

因此,第一个是BY FAR越好。

答案 4 :(得分:17)

我知道这是一个老问题,但我会回答这个问题,因为我不得不在这里回答所有答案。

现在,我同意大多数时候你要么做一个简单的throw,尽可能多地保存关于出错的信息,或者你想抛出一个可能包含的新异常这是一个内部异常,取决于你想知道导致它的内部事件的可能性。

但有一个例外。在某些情况下,方法将调用另一个方法,并且在内部调用中导致异常的条件应被视为外部调用中的相同异常。

一个例子是使用另一个集合实现的专门集合。假设它是DistinctList<T>包裹List<T>但拒绝重复的项目。

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

现在,你可能根本就没有抓住这个行为,让它通过。虽然用户在List<T>上调用某些内容时会从DistinctList<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行可能存在错误。< / p>

我们可以检查并包装:

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);
  }
}

对于可能存在错误的新代码而言,情况更糟。而且我们没有从内心异常中获得一些东西。如果我们将一个null数组传递给这个方法并接收ArgumentNullException,我们就不会通过检查内部异常并了解到对_innerList.CopyTo的调用是通过一个空数组并抛出一个来学习任何东西。 ArgumentNullException

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

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

如果用户使用不正确的参数调用它,我们期望必须抛出的每个异常将被重新抛出正确抛出。如果这里使用的逻辑中存在一个错误,那么它就是两行中的一行 - 要么我们错误地决定这种情况是这种方法有效,要么我们错误地将ArgumentException作为异常类型查找。这是catch块可能拥有的唯一两个错误。

现在。我仍然同意,大多数时候你要么想要一个简单的throw;,要么你想构建自己的异常,以便从问题方法的角度更直接地匹配问题。像上面这样的情况,像这样的重新投掷更有意义,还有很多其他情况。例如。举一个非常不同的例子,如果用FileStreamXmlTextReader实现的ATOM文件阅读器收到文件错误或无效的XML,那么它可能想要抛出从那些收到的完全相同的异常类,但它应该向调用者看AtomFileReader投掷FileNotFoundExceptionXmlException,因此他们可能会成为类似重新投掷的候选者。

编辑:

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

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;
  }
}

答案 5 :(得分:8)

你应该总是使用“throw;”重新抛出.NET中的异常,

请参阅此http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

基本上MSIL(CIL)有两条指令 - “throw”和“rethrow”以及C#的“throw ex”;被编译成MSIL的“throw”和C#的“throw”。 - 进入MSIL“重新抛出”!基本上我可以看到“throw ex”覆盖堆栈跟踪的原因。

答案 6 :(得分:4)

第一个更好。如果您尝试调试第二个并查看调用堆栈,您将看不到原始异常的来源。如果真的需要重新抛出,有一些技巧可以保持调用堆栈的完整性(尝试搜索,之前已经回答过)。

答案 7 :(得分:3)

这取决于。在调试版本中,我希望尽可能少地查看原始堆栈跟踪。在那种情况下,“扔;”符合条件。但是,在发布版本中,(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
            }
        }

问题措辞如何措辞,点击“投掷:”与“投掷前”;让它有点像红鲱鱼。真正的选择是在“投掷”之间和“抛出异常”,“扔前”;是一个不太可能的特殊情况“抛出异常。”

答案 8 :(得分:3)

我发现如果在捕获它的同一方法中抛出异常,则不会保留堆栈跟踪,因为它的价值。

void testExceptionHandling()
{
    try
    {
        throw new ArithmeticException("illegal expression");
    }
    catch (Exception ex)
    {
        throw;
    }
    finally
    {
        System.Diagnostics.Debug.WriteLine("finally called.");
    }
}