【问题标题】:How do you catch exceptions with "using" in C#如何在 C# 中使用“使用”捕获异常
【发布时间】:2009-02-05 22:34:24
【问题描述】:

鉴于此代码:

using (var conn = new SqlConnection("..."))
{
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = "...";
        using (var reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
                // ...
            }
        }
    }
}

我习惯于为我的数据访问编写 try/catch/finally 块,但是,我正在接触“使用”,这似乎是一种更简单的方法。但是,我正在尝试弄清楚如何捕获可能发生的异常。

能否举个例子说明如何捕获异常?

编辑添加:

我被引导相信“使用”是我的 try/catch/finally 块的替代品。我知道 using 不会捕获异常。那么这是如何替代的呢?

【问题讨论】:

    标签: c# sql ado.net


    【解决方案1】:

    using 不是为捕获异常而设计的;它旨在为您提供一种简单的方法来将 try/finally 包裹在需要处理的对象周围。如果您需要捕获和处理异常,则需要将其扩展为完整的 try/catch/finally 或在整个内容周围放置一个包含 try/catch 的内容。


    回答您的编辑(usingtry/catch/finally 的替代品吗?)然后不,不是。大多数情况下,当使用一次性资源时,您不会在那里处理异常,因为通常您无能为力。因此,它提供了一种方便的方法来确保清理资源,而不管您尝试执行的操作是否有效。

    通常,处理一次性资源的代码工作的级别太低,无法确定失败时的正确操作,因此异常会传播给可以决定采取什么操作的调用者(例如重试、失败、日志等)。 您倾向于将catch 块与一次性资源一起使用的唯一地方是,如果您要转换异常(我假设这就是您的数据访问层正在执行的操作)。

    【讨论】:

    • “大多数情况下,使用一次性资源时,您不会在那里处理异常,因为通常您无能为力。” - 是的,但是using 块也被(滥用)用于日志记录、编写结构化文件等等 - 因此能够在主题的Dispose 方法中获取异常详细信息确实有一些 (如果不明智)实用程序。
    【解决方案2】:

    这...

    using (var cmd = new SqlCommand("SELECT * FROM Customers"))
    {
        cmd.CommandTimeout = 60000;
        ...
    }
    

    ...几乎在语义上是相同的(即语法糖):

    {
        SqlCommand cmd = new SqlCommand("SELECT * FROM Customers");
        try
        {
            cmd.CommandTimeout = 60000;
            ...
        }
        finally
        {
            if (cmd is IDisposable disposable)
            {
                disposable.Dispose();
            }
        }
    }
    

    注意:

    • 无法重新分配 cmd 本地,这是 C# 具有“只读本地”(以及 foreach)的少数情况之一。
    • 外部大括号({})定义了一个匿名范围(也称为 复合语句 或只是一个“块”),它限制了 cmd 的词法范围,所以它在被释放后不能被名称引用(但如果你真的想要,你仍然可以给它起别名)。
    • 而编译器将执行“is-IDisposable" check at compile-time (otherwise the usingstatement will not compile), a hidden implicit conversion toIDisposable is necessary in case the subject (cmd) only _explicitly_ implements IDisposableinstead of via a publicvoid Dispose()`方法。

    因此,当人们告诉您“使用”是 try/catch/finally 的替代品时,他们是在暗示您应该使用长手形式,但在您的 catch 块中添加:

    var cmd = new SqlCommand("SELECT * FROM Customers");
    try
    {
        cmd.CommandTimeout = 60000;
        ...
    }
    catch (Exception ex)
    {
        ...//your stuff here
    }
    finally
    {
        if (cmd != null)
            cmd.Dispose();
    }
    

    【讨论】:

    • 如果在try块中创建了cmd,则不能在finally块中调用dispose。
    【解决方案3】:

    将所有 using 语句包装到 try/catch 中。就像其他人所说的那样,使用是为了清理实现 IDisposable 接口的类

    try
    {
    
     using (var conn = new SqlConnection("..."))
     {
        conn.Open();
        using (var cmd = conn.CreateCommand())
        {
            cmd.CommandText = "...";
            using (var reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    // ...
                }
            }
        }
     }
    }
    catch(Exception ex)
    {
    //Handle, log, rethrow exception
    }
    

    【讨论】:

    • 在我的 finally 块中 try/catch/finally 和调用 dispose 有什么好处?
    • Erik,你为什么要在 finally 块中调用 dispose?什么时候可以使用 using 语句?如果你使用 finally 块,那么你必须在 try 块之外声明对象...
    • 在内部这与使用语句将生成 Charles 的 IL 相同。它真的只是语法糖。
    • 起初我想不出那个解决方案,尝试在“使用”括号中编写“尝试”子句,然后编译器当然说 wtf 你在做什么 :D 。然后来到页面。并说“我的天哪,怎么会这样想”
    【解决方案4】:

    如果你想在那里捕获异常,你可能应该回到使用 try/catch/finally。只需将 .Dispose() 调用放在 finally 块中即可。

    【讨论】:

    • 他可以将 using 包装到 try/catch 块中。如果捕获到错误,则在进入 catch 之前会调用 Dispose(如果存在)。
    • 确实如此,但是你多了一层缩进,而 u​​sing 语句不再那么方便了。我宁愿只用一个 try/catch/finally 处理所有事情(但这是一种次要的风格)。
    • 但是即使使用简单的错误处理,也可以很容易地编写看起来像调用Dispose() 但在某些情况下会失败的代码。 using() 至少确保它被正确清理,即使它使细粒度异常处理几乎不可能。
    【解决方案5】:

    使用语句与异常无关。使用块只是确保在使用块中的对象退出该块时调用 Dispose。即:

    using(SqlConnection conn = new SqlConnection(conStr))
    {
       //use conn
    }//Dispose is called here on conn.
    

    如果打开连接引发异常(或该块中的任何其他内容),它仍然会冒泡到顶部,并且会像任何其他未经处理的异常一样。

    【讨论】:

      【解决方案6】:

      您仍然可以像以前一样捕获(或忽略)异常。关键是您不再需要担心处理数据库连接。

      即,如果您的应用程序要求您出于某些其他原因(例如,日志记录)捕获异常,请继续,但如果您只想处理数据库连接,则不再需要这样做:

      using (SqlConnection conn = new SqlConnection(...))
      {
          // do your db work here
          // whatever happens the connection will be safely disposed
      }
      

      如果你出于其他原因想要捕获异常,你仍然可以这样做:

      try
      {
          using (SqlConnection conn = new SqlConnection(...))
          {
              // do your db work here
              // whatever happens the connection will be safely disposed
          }
      }
      catch (Exception ex)
      {
          // do your stuff here (eg, logging)
          // nb: the connection will already have been disposed at this point
      }
      finally
      {
          // if you need it
      }
      

      【讨论】:

      • 这很有趣。我想知道你是否会在这里探出头来:)
      【解决方案7】:

      using 块/秒放入try catch 块中。他们隐含的 finally 语句专用于其对象处置将在外部块 catch 语句及其内容之前执行。

      【讨论】:

      • 这与其他答案有何不同,例如查克·康威的那个?
      • 它解释了隐含的“finally”语句将在外部块“catch”语句之前执行。我认为这是一个重要的重点,即使没有明确表达,我认为在最初的问题中也暗示了这一点,或者至少我掌握了这种需要
      猜你喜欢
      • 2011-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-30
      • 2013-12-13
      • 2021-05-23
      • 1970-01-01
      相关资源
      最近更新 更多