【问题标题】:Is one using-statement enough for all variables pointing to one IDisposable?对于指向一个 IDisposable 的所有变量,一个 using 语句是否足够?
【发布时间】:2013-06-19 19:48:04
【问题描述】:

例如:在方法中创建它并将其分配给一个字段。将该字段传递给方法,然后将其分配给 using 语句的变量(这是唯一被调用的 Dispose)。

SqlCommand CreateSqlCommand()
{
    SqlCommand cmd1 = new SqlCommand();
    return cmd1;
}

void UseSqlCommand(SqlCommand cmd4)
{
    using (SqlCommand cmd3 = cmd4)//Is this using-statement enough?
    {
        //use cmd3 here...
    }
}

并使用:

SqlCommand cmd2 = CreateSqlCommand();
UseSqlCommand(cmd2);

额外细节:GC 是否会在下一轮收集所有这些变量?为什么不呢 - 请参阅 David M. Kean 的回答 here

编辑

我已经添加了

cmd2.CommandText = "";

在前(最后)行之后。并且没有抛出任何错误。

为什么?它应该已经被处理掉了! 没关系。可以引用已处置的对象...

请不要专注于示例,而应专注于问题本身。谢谢。

【问题讨论】:

  • 这看起来真的很糟糕。假设 CLR 将在 using 块的末尾调用 Dispose(),您将有一些 cmd4 对象引用指向一个已处置的对象。请不要那样做。
  • 同意;您必须检查它是否没有像 HighScore 所说的那样每次都被丢弃。
  • @HighCore 我正在尝试创建一种方法来执行任何命令。所以它接收到一个已经创建的命令。
  • @HighCore cmd4只出现了两次cmd2 也是如此。
  • @ispiro 你不明白,是吗?变量名无关紧要。您的对象实例来自其他地方,您的方法正在调用Dispose()。调用此方法的任何其他方法都将处理此实例。这很容易出现异常。

标签: c# .net sql idisposable sqlcommand


【解决方案1】:

是的,在引用变量的块完成后,using 语句将调用 Dispose()。这是一件好事和坏事,假设您创建一个 sql 命令并将结果存储在变量 cmd 中,然后将该变量传递给另一个使用和处理 cmd 的方法。现在你被一个已释放的变量卡住了,如果你尝试使用它,它可能会抛出一个ObjectDisposedException

SqlCommand cmd = CreateSqlCommand();
UseSqlCommand(cmd);

//Uh oh, cmd can still be used, what if I tried to call UseSqlCommand(cmd) again?

在方法之外处理该对象会更加清晰和安全(就像 Jordão 发布的那样)。

using(SqlCommand cmd = CreateSqlCommand())
{
    UseSqlCommand(cmd);
}

现在您可以完全控制对象并限制其范围。

【讨论】:

    【解决方案2】:

    从外部控制using的范围:

    using (SqlCommand cmd2 = CreateSqlCommand()) {
      UseSqlCommand(cmd2);
    }
    
    ...
    
    void UseSqlCommand(SqlCommand cmd4) {
      // use cmd4 here...
    }
    

    也许将UseSqlCommand 重命名为不同的名称,例如ExecuteSqlCommand

    【讨论】:

    • 这听起来是个好主意,但我仍然想知道这个问题本身。
    【解决方案3】:

    using 语句的目的不是处理变量,而是处理对象实例。变量通常用于标识对象实例,但引用类型变量不保存对象——它们保存“对象标识符”。

    如果有人说例如var myPort = new System.Io.Ports.SerialPort("COM1", ...); myPort.Open()SerialPort 对象将要求系统让它使用COM1 串行端口在另行通知之前不要让其他任何人使用它。系统将为该端口生成一个句柄,并设置一些标志,以便仅允许具有该句柄的代码使用该端口。一旦对象被创建(比如系统任意为其分配了一个ID#8675309),系统就会将该ID存储到变量myPort中。

    当代码不再需要使用该串行端口时,重要的是有人告诉对象 #8675309 它不再需要,这样它可以反过来告诉系统它应该使 COM1 可用于其他应用程序。这通常可以通过调用Dispose 来完成,该变量包含对对象#8675309 的引用。一旦完成,每个持有对对象#8675309 的引用的变量都将持有对已调用Dispose 方法的对象的引用。请注意,Dispose 方法实际上不会影响任何这些变量(除非它们在方法本身的代码中被重写)。在调用之前持有“object #8675309”的任何变量将在之后继续这样做。该对象将释放其串行端口,因此存储这些变量的引用将不再有用,并且使用这些变量的代码可能希望将它们清除,但SerialPort 对象将不在乎方式或其他方式。

    【讨论】:

      【解决方案4】:

      我认为这就是你想要做的:

      public class MySqlClass : IDisposable
      {
          private SqlConnection conn { get; set; }
      
          public MySqlClass(string connectionstring) 
          {
              conn = new SqlConnection(connectionstring);
          }
      
          public void DoSomething1(string tsql)
          {
              using (SqlCommand comm = new SqlCommand(tsql, conn)) {
                  conn.Open();           
              }
          }
      
          public void DoSomething2(string tsql)
          {
              using (SqlCommand comm = new SqlCommand(tsql, conn)) {
                  conn.Open();           
              }
          }
      
          //DISPOSE STUFF HERE    
      }
      

      使用...

      using (MySqlClass MySQL = new MySqlClass()) 
      {
          MySQL.DoSomething1();
          MySQL.DoSomething2();
      }
      

      * 更新 * 更新 >>>> 示例

      【讨论】:

      • 我不明白你的代码。为什么有 cmd 字段?您提到的代码中完全忽略了它!
      • 更新了我的例子——例子!
      猜你喜欢
      • 1970-01-01
      • 2017-06-04
      • 2010-11-05
      • 2012-08-22
      • 1970-01-01
      • 1970-01-01
      • 2020-08-03
      • 2016-12-05
      • 1970-01-01
      相关资源
      最近更新 更多