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

时间:2012-01-09 16:17:39

标签: c# exception

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

GetLockOwnerInfo(...)

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

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

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

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

等等:

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

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

更高级别将SqlException包装到另一个BusinessRuleException中,以便包含一些特殊属性和其他详细信息,同时将“原始”例外包括为InnerException

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

更高级别将BusinessRuleException包装到另一个LockerException中,以便包含一些特殊属性和其他详细信息,同时将“原始”例外包括为InnerException

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

现在的问题是我想要捕获原始文件SqlException,以检查特定的错误代码。

但是没有办法“抓住内在的异常”:

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

我考虑在抛出SqlException时将其捕获,并将各种值复制到重新抛出的异常中 - 但该代码不依赖于Sql。它遇到SqlException,但它不依赖于SqlException。

我考虑过捕捉所有异常:

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

但这是可怕的代码。

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

6 个答案:

答案 0 :(得分:14)

我不想告诉你这个,但你不能抓住内在的例外。

您可以做的是检查一个。

我建议你抓住你的高级异常(我相信它是LockerException)并检查该异常的InnerException属性。检查类型,如果不是SqlException,请检查该异常的InnerException。走一遍,直到找到SqlException类型,然后获取所需的数据。

那就是说,我同意dasblinkenlight你应该考虑 - 如果可能的话 - 你的异常框架的重构。

答案 1 :(得分:12)

你需要c#6 / visual studio 2015才能使用谓词:

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

Official C# Try/Catch Documentation

答案 2 :(得分:6)

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

您应该向阻止SQLException的块添加代码,以便在那时检查e.Number = 247,然后向BusinessRuleException添加一些区别于BusinessRuleException抛出的SQLException的属性以某种有意义的方式回复非SQLExceptione.Number != 247247。例如,如果幻数catch (SQLException e) { var toThrow = new BusinessRuleException(e); if (e.Number == 247) { toThrow.DuplicateDetected = true; } throw toThrow; } 意味着您遇到了重复(此时我的一个纯粹推测),您可以这样做:

BusinessRuleException

稍后抓住DuplicateDetected时,您可以检查其SQLException属性,并采取相应行动。

编辑1 (响应数据库阅读代码无法检查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; } ,如下所示:

SetDuplicateDetectedFlag

这是不太理想的,因为它打破了封装,但至少它在一个地方完成。如果您需要检查其他类型的例外(例如,因为您添加了一个Web服务源),您可以将其添加到{{1}}方法,一切都可以再次运行。

答案 3 :(得分:2)

让外部应用程序层关心包装异常的细节是代码气味;包裹越深,气味越大。您现在将SqlException包装到dbException中的类可能被设计为将SqlClient公开为通用数据库接口。因此,该类应包括区分不同异常条件的手段。例如,它可能会定义一个dbTimeoutWaitingForLockException并决定在捕获SqlException时抛出它,并根据其错误代码确定存在锁定超时。在vb.net中,拥有一个公开ErrorCause枚举的dbException类型可能更清晰,因此可以说Catch Ex as dbException When ex.Cause = dbErrorCauses.LockTimeout,但不幸的是,异常过滤器在C#中不可用。

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

答案 4 :(得分:1)

好问题和好答案!

我只想补充已经给出的答案,并提出一些进一步的想法:

一方面,我同意dasblinkenlight和其他用户。如果您捕获一个异常来重新抛出另一个异常,并将原始异常设置为内部异常,那么除了维护方法的合同之外,您应该这样做。 (访问SQL Server是一个实现细节,调用者不是/不能/不可能知道,所以它不能预期会抛出SqlException(或DbException)。)

然而,应用这种技术会产生一些人应该注意的含义:

  • 您隐瞒了错误的根本原因。在您的示例中,您向来电者报告业务规则无效(?),违反(?)等,实际上访问数据库时遇到问题(如果DbException被允许进一步冒泡调用堆栈,则会立即清除。)
  • 您隐藏了最初发生错误的位置。捕获的异常的StackTrace属性将指向远离错误最初发生位置的catch-block。这可能会使调试变得非常困难,除非你采取 非常注意记录所有内部异常的堆栈跟踪。 (一旦将软件部署到生产环境中并且您无法附加软件,情况尤其如此 调试器......)
  

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

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

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

答案 5 :(得分:0)

正如其他偷窥者所说,您无法捕获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);
}