关于C#:重新抛出参数减去catch和不做任何事情之间的区别?

The difference between re-throwing parameter-less catch and not doing anything?

假设我在两个不同的程序集中有以下两个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//in assembly A
public class TypeA {
   // Constructor omitted
   public void MethodA
   {
     try {
       //do something
     }
     catch {
        throw;
     }
   }
}
//in assembly B
public class TypeB {
   public void MethodB
   {
     try {
       TypeA a = new TypeA();
       a.MethodA();
     }
     catch (Exception e)
       //Handle exception
     }
   }
}

在这种情况下,methoda中的try-catch只提升异常,但不真正处理它。在方法A中使用try-catch有什么好处吗?换句话说,这种类型的try-catch块和根本不使用的块之间有区别吗?


在您的示例中,这样做没有好处。但在某些情况下,只需要鼓吹出一个特定的例外。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    public void Foo()
    {
        try
        {
            // Some service adapter code

            // A call to the service
        }
        catch (ServiceBoundaryException)
        {
            throw;
        }
        catch (Exception ex)
        {
            throw new AdapterBoundaryException("some message", ex);
        }
    }

这使您能够轻松地识别异常发生在哪个边界。在这种情况下,您需要确保只针对特定于边界的代码抛出边界异常。


照目前的情况来看,第一种选择似乎很糟糕(或者应该是"无用的"?)想法。然而,很少这样做。异常通常在两种情况下从catch块中重新抛出:

a.您要检查为数据生成的异常,并有条件地将其添加到堆栈中。

1
2
3
4
5
6
7
8
9
10
11
12
try
{
  //do something
}
catch (Exception ex)
{
  //Check ex for certain conditions.
  if (ex.Message ="Something bad")
    throw ex;
  else
    //Handle the exception here itself.
}

B.组件中出现了不可接受的情况,需要将此信息传达给调用代码(通常通过附加一些其他有用的信息或将其包装在另一个异常类型中)。

1
2
3
4
5
6
7
8
9
10
try
{
  //do something
}
catch (StackOverflowException ex)
{
    //Bubble up the exception to calling code
    //by wrapping it up in a custom exception.
    throw new MyEuphemisticException(ex,"Something not-so-good just happened!");
}


是的,有区别。当捕获异常时,.NET假定您将以某种方式处理它,堆栈将展开到执行捕获的函数。

如果您不捕获它,它将以未处理的异常结束,这将调用某种诊断(如调试器或异常记录器),那么完整的堆栈及其在实际故障点的状态将可供检查。

因此,如果您捕获了异常,然后重新抛出一个在其他地方没有处理的异常,那么您将剥夺诊断工具关于实际发生的事情的真正有用的信息。


只是重新思考毫无意义——就像你什么都没做一样。

但是,当您实际执行某项操作时,它会很有用——最常见的是记录异常。您还可以更改类的状态,无论什么。


使用您为methoda编写的代码,没有区别。它所要做的就是占用处理器周期。但是,如果有一个您必须释放的资源,那么用这种方式编写代码是有好处的。例如

1
2
3
4
5
6
7
8
Resource r = GetSomeResource();
try {
  // Do Something
} catch {
  FreeSomeResource();
  throw;
}
FreeSomeResource();

但是这样做没有真正意义。最好使用finally块。


由于这些类位于两个不同的程序集中,您可能只需要捕获用于记录它的异常,然后将其抛出给调用方,以便它能够以它认为合适的方式处理它。一个throw而不是一个throw-ex将保存有关异常产生位置的上下文信息。当您的程序集是一个API/框架时,这可能会被证明是有用的,在这个API/框架中,除非有必要这样做,否则决不应吞咽异常,但如果将异常记录到事件日志中,这仍然有助于解决问题。


程序集a-try catch-block对我没有任何意义。我相信,如果您不打算处理异常,那么为什么要捕获这些异常。它无论如何都会被抛到下一个层次。

但是,如果您正在创建一个中间层API或类似的东西,并且在该层中处理一个异常(因此占用了异常)是没有意义的,那么您可以抛出自己的层applicationexception。当然,重新考虑同样的例外情况是没有意义的。


重新引发异常可用于将其封装为一般异常,如…考虑下面的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class XmlException: Exception{
   ....
}

public class XmlParser{
   public void Parse()
   {
      try{
          ....
      }
      catch(IOException ex)
      {
         throw new XmlException("IO Error while Parsing", ex );
      }
   }
}

这比对异常进行分类有好处。这就是ASPX文件处理程序和许多其他系统代码如何进行异常封装,从而确定它们到达堆栈的方式及其逻辑流。


当您捕获并抛出时,它允许您在throw行上设置断点。


不要做选项A。正如安东所说,它会吞噬堆栈跟踪。Jaredpar的例子也吞噬了stacktrace。更好的解决方案是:

1
2
3
4
5
6
SomeType* pValue = GetValue();
try {
  // Do Something
} finally {
  delete pValue;
}

如果您在c中有需要释放的内容,例如,一个文件流,您有以下两个选择:

1
2
3
4
5
6
7
8
9
10
11
FileStream stream;
try
{
  stream = new FileStream("C:\\afile.txt");
  // do something with the stream
}
finally
{
  // Will always close the stream, even if there are an exception
  stream.Close();
}

或者更干净:

1
2
3
4
using (FileStream stream = new FileStream("c:\\afile.txt"))
{
  // do something with the stream
}

using语句将在完成或关闭异常时释放(并关闭)流。


只有在方法A中可以捕获可以在methodA()中处理的特定异常(例如:日志记录)时,才能使用try catch(ex)block。

另一个选项是使用innerException属性链接异常并将其传递给调用方。这个想法不会杀死堆栈跟踪。