关于.net:在C#中重新抛出异常的正确方法是什么?

What is the proper way to re-throw an exception in C#?

本问题已经有最佳答案,请猛点这里访问。

我有一个问题要问你,那是因为我的搭档以不同于我的方式做事。

这样做更好吗:

1
2
3
4
5
6
7
8
9
try
{
    ...
}
catch (Exception ex)
{
    ...
    throw;
}

或者:

1
2
3
4
5
6
7
8
9
try
{
    ...
}
catch (Exception ex)
{
    ...
    throw ex;
}

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


您应该始终使用以下语法来重新引发异常,否则将跺脚堆栈跟踪:

1
throw;

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

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


我的偏好是使用

1
2
3
4
5
6
7
8
try
{
}
catch (Exception ex)
{
     ...
     throw new Exception ("Put more context here", ex)
}

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


如果抛出一个没有变量的异常(第二个示例),stacktrace将包含引发该异常的原始方法。

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

例子:

1
2
3
4
5
6
7
8
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
    }


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

因此,第一个是迄今为止更好的。


我知道这是一个古老的问题,但我会回答它,因为我必须不同意这里所有的答案。

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

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

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

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

现在,你完全无法理解被训斥的意思,让它过去。在这里,尽管用户在调用DistinctList时从List中得到一个异常。不是世界末日,但您可能希望隐藏这些实现细节。

或者你可以自己检查:

1
2
3
4
5
6
7
8
9
10
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行,其中可能存在错误。

我们可以检查并包装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
  }
}

就新添加的可能有问题的代码而言,情况更糟。我们并没有从内在的例外中得到什么。如果向此方法传递一个空数组并接收一个ArgumentNullException,我们将不会通过检查内部异常并了解到对_innerList.CopyTo的调用传递了一个空数组并抛出了一个ArgumentNullException,来学习任何东西。

在这里,我们可以随心所欲:

1
2
3
4
5
6
7
8
9
10
11
public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentException ae)
  {
    throw ae;
  }
}

如果用户使用不正确的参数调用异常,我们期望必须抛出的每个异常都将被重新抛出正确抛出。如果在这里使用的逻辑中有一个bug,它在两行中的一行中——要么我们在决定这是一个这种方法工作的情况时出错,要么我们在让ArgumentException作为异常类型查找时出错。这是catch块可能只有的两个bug。

现在。我仍然同意,大多数时候,您要么想要一个普通的throw;,要么想要构造自己的异常,以便从所讨论的方法的角度更直接地匹配问题。像上面这样的情况下,像这样重新投掷更有意义,还有很多其他的情况。例如,举一个非常不同的例子,如果一个使用FileStreamXmlTextReader实现的Atom文件阅读器接收到一个文件错误或无效的XML,那么它可能希望抛出从这些类接收到的完全相同的异常,但是它应该向调用者看,它是AtomFileReader正在抛出FileNotFoundException或edo。cx1〔19〕,因此他们可能是类似的再投的候选人。

编辑:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;"重新引发.NET中的异常,

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

基本上,msil(cil)有两条指令——"throw"和"rethrow"和"c"throw ex;"编译成msil的"throw"和"c"throw;"-编译成msil的"rethrow"!基本上,我可以看到为什么"throw-ex"会覆盖堆栈跟踪。


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


我发现,如果在捕获异常的同一方法中抛出异常,那么堆栈跟踪就不会被保留,这是值得的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void testExceptionHandling()
{
    try
    {
        throw new ArithmeticException("illegal expression");
    }
    catch (Exception ex)
    {
        throw;
    }
    finally
    {
        System.Diagnostics.Debug.WriteLine("finally called.");
    }
}

这要看情况而定。在调试构建中,我希望尽可能少地查看原始堆栈跟踪。在这种情况下,"扔";符合要求。然而,在一个发布版本中,(a)我想用包含原始堆栈跟踪的日志记录错误,一旦记录完成,(b)重新设置错误处理,以便对用户更有意义。这里"抛出异常"是有意义的。确实,重新引发错误会丢弃原始堆栈跟踪,但非开发人员从查看堆栈跟踪信息中一无所获,因此可以重新引发错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        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:"vs"throw ex";让它有点像是一条红鲱鱼。真正的选择是在"throw;"和"throw exception"之间,其中"throw ex;"是"throw exception"不太可能出现的特殊情况。