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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8791539/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 04:26:41  来源:igfitidea点击:

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

c#exception

提问by Ian Boyd

i'm calling a function that throws a custom exception:

我正在调用一个抛出自定义异常的函数:

GetLockOwnerInfo(...)

This function in turn is calling a function that throws an exception:

这个函数反过来调用一个抛出异常的函数:

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

This function in turn is calling a function that throws an exception:

这个函数反过来调用一个抛出异常的函数:

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 SqlExceptionis.

其中一个函数抛出一个SqlException,尽管该代码不知道 anSqlException是什么。

Higher levels wrap that SqlExceptioninto another BusinessRuleExceptionin 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 BusinessRuleExceptioninto another LockerExceptionin 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 SqlExceptionright when it's thrown, and copy various values to the re-thrown exception - but that code is not dependant on Sql. It is experiencing an SqlException, but it has no dependency on SqlException.

我想过SqlException在抛出时立即捕获,并将各种值复制到重新抛出的异常中 - 但该代码不依赖于 Sql。它正在经历一个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 Messageof an Exceptionto include additional information, what is the intended mechanism to catch original exceptions?

鉴于 .NET 不允许您更改MessageanException以包含其他信息,那么捕获原始异常的预期机制是什么?

采纳答案by Randolpho

I hate to have to tell you this, but you cannot catch an inner exception.

我不想告诉你这一点,但你无法捕捉到内在的异常。

What you can do is inspectone.

你能做的就是检查一个。

I suggest you catch your high-level exception (I believe it was LockerException) and inspect the InnerExceptionproperty of that exception. Check the type, and if it's not a SqlException, check the InnerExceptionof that exception. Walk each one until you find a SqlExceptiontype, then get the data you need.

我建议您捕获高级异常(我相信是LockerException)并检查该InnerException异常的属性。检查类型,如果它不是 a SqlException,则检查该InnerException异常的 。遍历每一个直到找到一种SqlException类型,然后获取所需的数据。

That said, I agree with dasblinkenlight that you should consider -- if possible -- a heavy refactor of your exception framework.

也就是说,我同意 dasblinkenlight 您应该考虑——如果可能的话——对您的异常框架进行大量重构。

回答by 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. It would throw something other than SQLExceptionunder the same condition, and your outer code would have no way to detect it.

检查包装异常的错误代码不是一个好习惯,因为它严重损害了封装。想象一下,在某个时刻重写逻辑以从非 SQL 源(例如 Web 服务)读取数据。除了SQLException在相同条件下,它会抛出其他东西,而您的外部代码将无法检测到它。

You should add code to the block catching SQLExceptionto check for e.Number = 247right then and there, and throw BusinessRuleExceptionwith some property that differentiates it from BusinessRuleExceptionthrown in response to non-SQLExceptionand SQLExceptionwith e.Number != 247in some meaningful way. For example, if the magic number 247means you've encountered a duplicate (a pure speculation on my part at this point), you could do something like this:

您应该将代码添加到块捕获SQLException以检查e.Number = 247当时和那里,并抛出BusinessRuleException一些属性,以某种有意义的方式将其与BusinessRuleException响应 non-SQLExceptionSQLExceptionwith 的抛出区分开e.Number != 247来。例如,如果幻数247意味着您遇到了重复(此时我纯属猜测),您可以执行以下操作:

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

When you catch BusinessRuleExceptionlater, you can check its DuplicateDetectedproperty, 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 BusinessRuleExceptionto check for SQLExceptionin 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 (e.g. because you've added a web service source), you could add it to the SetDuplicateDetectedFlagmethod, and everything would work again.

这是不太可取的,因为它破坏了封装,但至少它在一个地方做到了。如果您需要检查其他类型的异常(例如,因为您添加了一个 Web 服务源),您可以将它添加到SetDuplicateDetectedFlag方法中,然后一切都会再次运行。

回答by supercat

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 SqlExceptioninto a dbExceptionis presumably designed to expose an SqlClient as a generic database interface. 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. 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#.

让外部应用层关心包装异常的细节是一种代码味道;包裹得越深,气味越大。您现在将 包装SqlException到 adbException中的类可能旨在将 SqlClient 作为通用数据库接口公开。因此,该类应包括区分不同例外情况的方法。例如,它可以定义一个 dbTimeoutWaitingForLockException 并决定在捕获 SqlException 时抛出它,并根据其错误代码确定存在锁定超时。在 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.

如果有一种情况,内部类包装器不知道它在做什么以知道它应该如何映射异常,那么让内部类方法接受一个将接受异常的异常包装委托可能会有所帮助内部类已经捕获或“想要”抛出,并以适合外部类的方式包装它。在直接从外部类调用内部类的情况下,这种方法可能会有些过头,但如果涉及中间类,则可能会很有用。

回答by afrischke

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. 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 DbExceptionfor that matter) will be thrown.)

一方面,我同意 dasblinkenlight 和其他用户的观点。如果您捕获一个异常以重新抛出不同类型的异常,并将原始异常设置为内部异常,那么除了维护方法的约定之外,您不应出于其他原因执行此操作。(访问 SQL 服务器是调用者不知道/必须不知道/不能知道的实现细节,因此它无法预期会抛出SqlException(或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 DbExceptionwere allowed to bubble up the call stack further).
  • You are concealing the location where the error originally occurred.The StackTraceproperty of the caught exception will point to a catch-block far away from the location the error originally occurred. 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...)
  • 您隐藏了错误的根本原因。在您的示例中,您向调用者报告业务规则无效(?),违反(?)等,而实际上访问数据库时出现问题(如果DbException允许冒泡,这将立即清楚进一步调用堆栈)。
  • 您正在隐藏最初发生错误的位置。StackTrace捕获异常的属性将指向远离最初发生错误位置的捕获块。除非您非常小心地记录所有内部异常的堆栈跟踪,否则这会使调试变得非常困难。(尤其是在将软件部署到生产环境中并且您无法附加调试器时更是如此……)

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. It provides another mechanism however to supply additional information to an Exception via the Exception.Datadictionary. 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:

.NET 确实不允许您更改异常消息。然而,它提供了另一种机制来通过Exception.Data字典向异常提供附加信息。因此,如果您只想向异常添加额外数据,则没有理由包装原始异常并抛出新异常。而是只做:

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

回答by Timothy Gonzalez

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 文档

回答by Jeremy

As other peeps say, you cannot catch an the InnerException. A function such as this could help you get the InnerException out of the tree though:

正如其他人所说,您无法捕捉到 InnerException。像这样的函数可以帮助您从树中获取 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);
}