简体   繁体   English

如何在 C# 中捕获原始(内部)异常?

[英]How to catch the original (inner) exception in C#?

i'm calling a function that throws a custom exception:我正在调用一个引发自定义异常的 function:

GetLockOwnerInfo(...)

This function in turn is calling a function that throws an exception:这个 function 又调用了一个抛出异常的 function:

GetLockOwnerInfo(...)
   ExecuteReader(...)

This function in turn is calling a function that throws an exception:这个 function 又调用了一个抛出异常的 function:

GetLockOwnerInfo(...)
   ExecuteReader(...)
      ExecuteReader(...)

And so on:等等:

GetLockOwnerInfo(...)
   ExecuteReader(...)
      ExecuteReader(...)
         ExecuteReaderClient(...)
             Fill(...)

One of these functions throws an SqlException , although that code has no idea what an SqlException is.其中一个函数会引发SqlException ,尽管该代码不知道SqlException是什么。

Higher levels wrap that SqlException into another BusinessRuleException in order to include some special properties and additional details, while including the "original" exception as InnerException :更高级别将该SqlException包装到另一个BusinessRuleException中,以包含一些特殊属性和其他详细信息,同时将“原始”异常包括为InnerException

catch (DbException ex)
{
    BusinessRuleExcpetion e = new BusinessRuleException(ex)
    ...
    throw e;
}

Higher levels wrap that BusinessRuleException into another LockerException in order to include some special properties and additional details, while including the "original" exception as InnerException :更高级别将该BusinessRuleException包装到另一个LockerException中,以包含一些特殊属性和其他详细信息,同时将“原始”异常包含为InnerException

catch (BusinessRuleException ex)
{
    LockerException e = new LockerException(ex)
    ...
    throw e;
}

The problem now is that i want to catch the origianl SqlException , to check for a particular error code.现在的问题是我想捕获原始的SqlException ,以检查特定的错误代码。

But there's no way to "catch the inner exception":但是没有办法“捕捉到内部异常”:

try
{
   DoSomething();
}
catch (SqlException e)
{
   if (e.Number = 247) 
   {
      return "Someone";
   }
   else
      throw;
}

i thought about catching SqlException right when it's thrown, and copy various values to the re-thrown exception - but that code is not dependant on Sql.我想过在SqlException时立即捕获它,并将各种值复制到重新抛出的异常中 - 但该代码不依赖于 Sql。 It is experiencing an SqlException , but it has no dependency on SqlException.它正在遇到SqlException ,但它不依赖于 SqlException。

i thought about catching all exceptions:我想过捕获所有异常:

try
{
   DoSomething(...);
}
catch (Exception e)
{
   SqlException ex = HuntAroundForAnSqlException(e);
   if (ex != null)
   {
      if (e.Number = 247) 
      {
          return "Someone";
      }
      else
         throw;
   }
   else
      throw;
}

But that's horrible code.但那是可怕的代码。

Given that .NET does not let you alter the Message of an Exception to include additional information, what is the intended mechanism to catch original exceptions?鉴于 .NET 不允许您更改Exception Message以包含其他信息,那么捕获原始异常的预期机制是什么?

You need c# 6 / visual studio 2015 in order to do this using a predicate:您需要 c# 6 / Visual Studio 2015 才能使用谓词执行此操作:

catch (ArgumentException e) when (e.ParamName == “…”)
{
}

Official C# Try/Catch Documentation 官方 C# Try/Catch 文档

I hate to have to tell you this, but you cannot catch an inner exception.我不想告诉你这一点,但你无法捕捉到内在的异常。

What you can do is inspect one.你能做的就是检查一个。

I suggest you catch your high-level exception (I believe it was LockerException ) and inspect the InnerException property of that exception.我建议您捕获高级异常(我相信它是LockerException )并检查该InnerException属性。 Check the type, and if it's not a SqlException , check the InnerException of that exception.检查类型,如果它不是SqlException ,请检查该InnerException Walk each one until you find a SqlException type, then get the data you need.遍历每一个,直到找到SqlException类型,然后获取所需的数据。

That said, I agree with dasblinkenlight that you should consider -- if possible -- a heavy refactor of your exception framework.也就是说,我同意 dasblinkenlight 您应该考虑——如果可能的话——对您的异常框架进行大量重构。

Checking the error code of a wrapped exception is not a good practice, because it hurts encapsulation rather severely.检查包装异常的错误代码不是一个好习惯,因为它严重损害了封装。 Imagine at some point rewriting the logic to read from a non-SQL source, say, a web service.想象一下,在某个时刻重写逻辑以从非 SQL 源(例如 Web 服务)读取数据。 It would throw something other than SQLException under the same condition, and your outer code would have no way to detect it.在相同的条件下它会抛出SQLException以外的东西,你的外部代码将无法检测到它。

You should add code to the block catching SQLException to check for e.Number = 247 right then and there, and throw BusinessRuleException with some property that differentiates it from BusinessRuleException thrown in response to non- SQLException and SQLException with e.Number != 247 in some meaningful way.您应该将代码添加到捕获SQLException的块中,以立即检查e.Number = 247 ,并抛出带有一些属性的BusinessRuleException以区别于响应非SQLExceptionSQLException引发的BusinessRuleExceptione.Number != 247 in一些有意义的方式。 For example, if the magic number 247 means you've encountered a duplicate (a pure speculation on my part at this point), you could do something like this:例如,如果幻数247表示您遇到了重复项(此时我纯属猜测),您可以执行以下操作:

catch (SQLException e) {
    var toThrow = new BusinessRuleException(e);
    if (e.Number == 247) {
        toThrow.DuplicateDetected = true;
    }
    throw toThrow;
}

When you catch BusinessRuleException later, you can check its DuplicateDetected property, and act accordingly.当您稍后捕获BusinessRuleException ,您可以检查其DuplicateDetected属性,并采取相应的行动。

EDIT 1 (in response to the comment that the DB-reading code cannot check for SQLException )编辑 1 (响应 DB 读取代码无法检查SQLException

You can also change your BusinessRuleException to check for SQLException in its constructor, like this:您还可以更改BusinessRuleException以检查其构造函数中的SQLException ,如下所示:

public BusinessRuleException(Exception inner)
:   base(inner) {
    SetDuplicateDetectedFlag(inner);
}

public BusinessRuleException(string message, Exception inner)
:   base(message, inner) {
    SetDuplicateDetectedFlag(inner);
}

private void SetDuplicateDetectedFlag(Exception inner) {
    var innerSql = inner as SqlException;
    DuplicateDetected = innerSql != null && innerSql.Number == 247;
}

This is less desirable, because it breaks encapsulation, but at least it does it in a single place.这是不太可取的,因为它破坏了封装,但至少它在一个地方做到了。 If you need to examine other types of exceptions (eg because you've added a web service source), you could add it to the SetDuplicateDetectedFlag method, and everything would work again.如果您需要检查其他类型的异常(例如,因为您添加了一个 Web 服务源),您可以将其添加到SetDuplicateDetectedFlag方法中,然后一切都会再次运行。

Having an outer application layer care about the details of a wrapped exception is a code smell;让外部应用层关心包装异常的细节是一种代码味道; the deeper the wrapping, the bigger the smell.包裹得越深,气味越大。 The class which you now have wrapping the SqlException into a dbException is presumably designed to expose an SqlClient as a generic database interface.你现在已经包裹类SqlExceptiondbException据推测设计而露出的SqlClient作为一个通用的数据库接口。 As such, that class should include a means of distinguishing different exceptional conditions.因此,该类应包括区分不同例外情况的方法。 It may, for example, define a dbTimeoutWaitingForLockException and decide to throw it when it catches an SqlException and determines based upon its error code that there was a lock timeout.例如,它可以定义一个 dbTimeoutWaitingForLockException 并决定在捕获 SqlException 时抛出它,并根据其错误代码确定存在锁定超时。 In vb.net, it might be cleaner to have a dbException type which exposes an ErrorCause enumeration, so one could then say Catch Ex as dbException When ex.Cause = dbErrorCauses.LockTimeout , but unfortunately exception filters are not usable in C#.在 vb.net 中,拥有一个公开 ErrorCause 枚举的 dbException 类型可能会更清晰,因此可以说Catch Ex as dbException When ex.Cause = dbErrorCauses.LockTimeout ,但不幸的是异常过滤器在 C# 中不可用。

If one has a situation where the inner-class wrapper won't know enough about what it's doing to know how it should map exceptions, it may be helpful to have the inner-class method accept an exception-wrapping delegate which would take an exception the inner class has caught or would "like" to throw, and wrap it in a way appropriate to the outer class.如果有一种情况,内部类包装器不知道它在做什么以知道它应该如何映射异常,那么让内部类方法接受一个将接受异常的异常包装委托可能会有所帮助内部类已经捕获或“想要”抛出,并以适合外部类的方式包装它。 Such an approach would likely be overkill in cases where the inner class is called directly from the outer class, but can be useful if there are intermediate classes involved.在直接从外部类调用内部类的情况下,这种方法可能会有些过头,但如果涉及中间类,则可能会很有用。

Good question and good answers!好问题和好答案!

I just want to supplement the answers already given with some further thoughts:我只想用一些进一步的想法来补充已经给出的答案:

On one hand I agree with dasblinkenlight and the other users.一方面,我同意 dasblinkenlight 和其他用户的观点。 If you catch one exception to rethrow an exception of a different type with the original exception set as the inner exception then you should do this for no other reason than to maintain the method's contract .如果您捕获一个异常以重新抛出不同类型的异常,并将原始异常设置为内部异常,那么除了维护方法的约定之外,您不应出于其他原因执行此操作。 (Accessing the SQL server is an implementation detail that the caller is not/must not/cannot be aware of, so it cannot anticipate that a SqlException (or DbException for that matter) will be thrown.) (访问 SQL 服务器是调用者不知道/必须不知道/不能知道的实现细节,因此它无法预期会SqlException DbException (或DbException )。)

Applying this technique however has some implications that one should be aware of:然而,应用这种技术有一些应该注意的含义:

  • You are concealing the root cause of the error.您隐藏了错误的根本原因。 In your example you are reporting to the caller that a business rule was invalid(?), violated(?) etc., when in fact there was a problem accessing the DB (which would be immediately clear if the DbException were allowed to bubble up the call stack further).在您的示例中,您向调用者报告业务规则无效(?),违反(?)等,而实际上访问数据库时出现问题(如果允许DbException冒泡,这将立即清楚调用堆栈进一步)。
  • You are concealing the location where the error originally occurred.您正在隐藏最初发生错误的位置。 The StackTrace property of the caught exception will point to a catch-block far away from the location the error originally occurred.捕获异常的StackTrace属性将指向远离最初发生错误位置的捕获块。 This can make debugging notoriously difficult unless you take great care to log the stack traces of all the inner exceptions as well.除非您非常小心地记录所有内部异常的堆栈跟踪,否则这会使调试变得非常困难。 (This is especially true once the software has been deployed into production and you have no means to attach a debugger...) (尤其是在将软件部署到生产环境中并且您无法附加调试器时更是如此……)

Given that .NET does not let you alter the Message of an Exception to include additional information, what is the intended mechanism to catch original exceptions?鉴于 .NET 不允许您更改异常消息以包含附加信息,那么捕获原始异常的预期机制是什么?

It is true that .NET does not allow you to alter the Message of an Exception. .NET 确实不允许您更改异常消息。 It provides another mechanism however to supply additional information to an Exception via the Exception.Data dictionary.然而,它提供了另一种机制来通过Exception.Data字典向异常提供附加信息。 So if all you want to do is add additional data to an exception, then there is no reason to wrap the original exception and throw a new one.因此,如果您只想向异常添加额外数据,则没有理由包装原始异常并抛出新异常。 Instead just do:而是只做:

public void DoStuff(String filename)
{
    try {
       // Some file I/O here...
    }
    catch (IOException ex) {

      // Add filename to the IOException
      ex.Data.Add("Filename", filename);

      // Send the exception along its way
      throw;
    }
}

As other peeps say, you cannot catch an the InnerException.正如其他人所说,您无法捕捉到 InnerException。 A function such as this could help you get the InnerException out of the tree though:像这样的函数可以帮助您从树中获取 InnerException :

public static bool TryFindInnerException<T>(Exception top, out T foundException) where T : Exception
{
    if (top == null)
    {
        foundException = null;
        return false;
    }

    Console.WriteLine(top.GetType());
    if (typeof(T) == top.GetType())
    {
        foundException = (T)top;
        return true;
    }

    return TryFindInnerException<T>(top.InnerException, out foundException);
}

I agree with the other comments that this is a code smell and should be avoided.我同意其他评论,即这是一种代码味道,应该避免。 But if a refactor is not possible you could try something like this...但是如果重构是不可能的,你可以尝试这样的事情......

Create an extension method...创建扩展方法...

public static bool HasInnerException(this Exception ex, Func<Exception, bool> match)
{
   if (ex.InnerException == null)
   {
      return false;
   }

   return match(ex.InnerException) || HasInnerException(ex.InnerException, match);
}

And use it like...并像使用它一样...

catch (Exception ex) when (ex.HasInnerException(e => e is MyExceptionThatIsHidden))
{ 
   ...

But really you should be solving for但实际上你应该解决

var exception = new Exception("wrapped exception 3",
   new Exception("wrapped exception 2",
      new Exception("wrapped exception 1",
         new MyExceptionThatIsHidden("original exception")))); // <--- ???

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM