检查内部异常的类型

20
在我的代码中,我遇到了一个情况,即会抛出一个System.Reflection.TargetInvocationException异常。在某些特定情况下,我知道如何处理根异常,但我希望抛出所有其他异常。我可以想到两种方法来实现这一点,但我不确定哪种更好。
1.
try
{
    //code
}
catch (System.Reflection.TargetInvocationException ex)
{
    if (typeof(ex.InnerException) == typeof(SpecificException))
    {
        //fix
    }
    else
    {
        throw ex.Innerexception;
    }
}

2.

try
{
    //code
}
catch (System.Reflection.TargetInvocationException ex)
{
    try
    {
        throw ex.InnerException;
    }
    catch (SpecificException exSpecific)
    {
        //fix
    }
}

我知道通常抛出异常会很慢,所以我认为第一个方法可能更快。或者,有没有我没想到的更好的方法?


2
在编程方面,2是有趣的,但在我看来,1更易读,也可能从性能角度来看更好。 - Gabber
问题:是哪个调用引发了“TargetInvocationException”?是你的代码还是第三方的? - Dan Puzey
这是由程序生成的代码,正在从数据库中读取数据。 - geekchic
3个回答

40
你提出的每种解决方案都有自己的问题。
第一种方法检查内部异常的类型是否与你期望的类型完全相同。这意味着派生类型将无法匹配,这可能不是你想要的。
第二种方法使用当前堆栈位置覆盖内部异常的堆栈跟踪,正如Dan Puzey所提到的那样。破坏堆栈跟踪可能会破坏你需要修复漏洞的唯一线索。
解决方案基本上是DarkGray发布的,加上Nick的建议和我自己的建议(在else中):
try 
{ 
    // Do something
} 
catch (TargetInvocationException ex) 
{ 
    if (ex.InnerException is SpecificException) 
    { 
        // Handle SpecificException
    }
    else if (ex.InnerException is SomeOtherSpecificException)
    {
        // Handle SomeOtherSpecificException
    }
    else 
    { 
        throw;    // Always rethrow exceptions you don't know how to handle.
    } 
}

如果你想重新抛出一个异常,但实际上并不能处理它,请不要使用“throw ex;”,因为这会覆盖掉调用栈信息。相反,使用“throw;”可以保留调用栈信息。它基本上意味着“我实际上并不想进入这个catch子句,请假装我从来没有捕获过这个异常”。
更新:C# 6.0通过“异常过滤器”提供了更好的语法:
try
{
    // Do something
}
catch (TargetInvocationException ex) when (ex.InnerException is SpecificException)
{
    // Handle SpecificException
}
catch (TargetInvocationException ex) when (ex.InnerException is SomeOtherSpecificException)
{
    // Handle SomeOtherSpecificException
}

3
感谢指出throw;throw ex;之间的差异,给你点个赞(+1)。 - geekchic
1
如果您在catch块中需要引用内部异常,给内部异常命名也是很有帮助的。例如,catch (DBUpdateException ex) when (ex.InnerException is PostgresException inner) { if (inner.Whatever) throw; } - SteveC

1

你的第二个方案确实是一个有趣的解决方案!

不过你需要小心:当TargetInvocationException首次捕获InnerException时,通常会由另一个组件抛出。如果你throw ex.InnerException,你将破坏它包含的一些信息(如堆栈跟踪),因为你正在从不同的位置重新抛出它。

所以在你提出的两个方案中,我肯定建议选择#1。在你目前的结构中,我不知道是否有其他替代方案。然而,InnerException最初可能已经在其他地方抛出了-值得探究是否有更优雅的地方来处理这个失败,更接近异常抛出的地方。


-2
try 
{ 
    //code 
} 
catch (System.Reflection.TargetInvocationException ex) 
{ 
    if (ex.InnerException is SpecificException) 
    { 
        //fix 
    } 
    else 
    { 
        throw ex.InnerException; 
    } 
} 

或者

try 
{ 
    //code 
} 
catch (System.Reflection.TargetInvocationException ex) 
{ 
    SpecificException spExc = ex.InnerException as SpecificException;
    if (spExc != null) 
    { 
        bla-bla spExc 
    } 
    else 
    { 
        throw ex.InnerException; 
    } 
} 

或者

try 
{ 
    //code 
} 
catch (System.Reflection.TargetInvocationException ex) 
{ 
    if (ex.InnerException.GetType() == typeof(SpecificException)) 
    { 
        //fix 
    } 
    else 
    { 
        throw ex.InnerException; 
    } 
} 

1
为什么不使用is关键字? - Nick
你的代码在功能上与原始的第一个建议没有任何区别,而且你也没有提供任何理由或证明该代码的合理性。 - Dan Puzey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接