使用“使用”和“最后”来清理资源

时间:2011-03-31 14:28:21

标签: .net using try-finally

是否需要以下结构?

using (Something something = new Something())
{
    try
    {
    }
    finally
    {
        something.SomeCleanup();
    }
}

或者,所有清理任务是否应在隐式something.Dispose()调用中执行?


以下是有问题的代码:

public static DataTable GetDataTable(string cmdText, IEnumerable<Parameter> parameters)
{
    // Create an empty memory table.
    DataTable dataTable = new DataTable();

    // Open a connection to the database.
    using (SqlConnection connection = new SqlConnection(ConfigurationTool.ConnectionString))
    {
        connection.Open();

        // Specify the stored procedure call and its parameters.
        using (SqlCommand command = new SqlCommand(cmdText, connection))
        {
            command.CommandType = CommandType.StoredProcedure;

            SqlParameterCollection parameterCollection = command.Parameters;
            foreach (Parameter parameter in parameters)
                parameterCollection.Add(parameter.SqlParameter);

            try
            {
                // Execute the stored procedure and retrieve the results in the table.
                using (SqlDataAdapter dataAdapter = new SqlDataAdapter(command))
                    try
                    {
                        dataAdapter.Fill(dataTable);
                    }
                    catch
                    {
                        dataTable.Dispose();
                        dataTable = null;
                    }
            }
            finally
            {
                //parameterCollection.Clear();
            }
        }
    }

    return dataTable;
}

注意:我已定义了Parameter类,因此此函数的用户无需直接处理SqlParameter的创建。 SqlParameter类的Parameter属性可用于检索SqlParameter

在某些时候,我的程序执行以下操作(无法发布代码,因为它涉及很多类;基本上,我有一个创建大量对象的迷你框架):

  1. 创建一个Parameter s。
  2. 数组
  3. GetDataTable('sp_one', parameters)
  4. GetDataTable('sp_two', parameters)

3 个答案:

答案 0 :(得分:5)

using关键字调用.Dispose()方法。如果您在IDisposable对象上的dispose方法之外进行必要的清理,那么您需要在它自己的finally块中执行此操作。这提出了两点:

  1. 此时,您可能会争辩说您也可以跳过using块并在finally块中调用Dispose()。就个人而言,我仍然会使用using块。这是一个很好的习惯,总是为你的IDisposable实例提供一个。
  2. 我谦卑地建议,如果你了解上述条件,你需要重新设计你的课程以利用IDisposable模式。

  3. 根据您发布的代码,问题是您的参数仍然存在于某个地方(也许您正在重新使用它们?)。由于参数仍然是有根的,因此无法收集。它们还包含对它们所附加命令的引用,因此您的SqlCommand对象也不能立即收集,因为它现在仍然是根的。

    关键是.Net框架为 unamanaged 资源保留Dispose()模式。由于SqlParameters和SqlParameterCollection是托管类型,因此在收集它们之前不会触及它们,这与处理完全分开。最后收集SqlCommand时,它的SqlParameter集合也会被处理掉。只是不要混淆收集,处置和他们的目的。

    您要做的是在添加每个参数时复制每个参数,而不是将现有参数添加到集合中。

    public static DataTable GetDataTable(string cmdText, IEnumerable<Parameter> parameters)
    {
        // Create an empty memory table.
        DataTable dataTable = new DataTable();
    
        // Prepare a connection to the database and command to execute.
        using (SqlConnection connection = new SqlConnection(ConfigurationTool.ConnectionString))
        using (SqlCommand command = new SqlCommand(cmdText, connection))
        {
            command.CommandType = CommandType.StoredProcedure;
    
            SqlParameterCollection parameterCollection = command.Parameters;
            foreach (Parameter parameter in parameters)
                parameterCollection.Add(CloneParameter(parameter.SqlParameter));
    
            // Execute the stored procedure and retrieve the results in the table.
            using (SqlDataAdapter dataAdapter = new SqlDataAdapter(command))
            {
                 dataAdapter.Fill(dataTable);
            }
        }
    
        return dataTable;
    }
    

    有些事情需要注意:我能够摆脱所有你的尝试块。不需要其中一个。此外,SqlDataAdapter.Fill()方法将为您打开和关闭连接,因此您不需要该部分。

    现在让我们来谈谈CloneParameter()函数。我得到的印象是你觉得它违背了代码的目的,即尝试重用参数。我保证你在这里重复使用参数是一个坏主意。性能损失可以忽略不计,特别是与存储过程执行相比。我将CloneParameter()实现留给了你,原因有二:首先它是微不足道的,其次是我们已经超出了我的正常数据访问模式。我通常做的添加参数是接受Action&lt; SqlParameterCollection&gt;委托,而不是可枚举的参数。声明的函数更像是这样:

    public IEnumerable<IDataRecord>GetData(string cmdText, Action<SqlParameterCollection> addParameters)
    

    并且被称为:

    foreach(var record in GetData("myprocedurename", p => 
      {
          p.Add( /*new parameter here*/ );
          p.Add( /*new parameter here*/ );
        //...
      })
     .Select( /*Returning a IEnumerable rather than a datatable allows me to use it with linq to objects.*/
              /* For example, you could use this spot to convert from DataRecords returned by ADO.Net to business objects */ 
            ))
    {
       // use the results here...
    }
    

    由于你连续填写两个表,听起来你有一些工作要做客户端,这可能证明与DataReader / IEnumerable方法相比,但我确实想提到这一点,因为大部分时间都是基于您在DataReader上的代码是更好的选择。

    对于我现有的基于Action-delegate的模式,我希望尽可能多地重复使用一组重复的参数,我会做一个真实的命名方法,知道如何添加参数和匹配我的行动代表。然后我可以直接传递该方法,并重新使用所需的参数。

答案 1 :(得分:4)

有趣的问题!

这一切都取决于你的Something课程。如果它的设计很差并且需要进行多阶段清理,它就会强迫它的客户特质。

你不应该设计类似的那样。如果要进行临时清理,请将它们封装在自己的类中,并使用如下代码:

using (Something something = new Something()) {
  // ...
  using (SomethingElse somethingElse = something.GiveMeSomethingElse()) {
  }
  // ...
} 

<强>更新

对于您的示例,它可能如下所示:

using (SqlConnection connection = new SqlConnection(connectionString)) {
  connection.Open();

  using (SqlCommand command = new SqlCommand("select * from MyTable where id = @id", connection)) {

    // to "reuse" the parameters collection population, just extract this to a separate method      
    command.Parameters.Add(new SqlParameter("id", id));

    // ... execute the command

  }

}

更新2:

这样做:

GetDataTable('sp_one', CreateParameters());
GetDataTable('sp_two', CreateParameters());

答案 2 :(得分:1)

Dispose应该清理所有非托管资源。例如,使用另一种清理方法来执行某些功能或数据库操作是完全可能的。