关于使用“使用”和“最后”清理资源

发布于 2024-10-28 19:40:44 字数 2095 浏览 4 评论 0原文

是否存在需要以下结构的情况?

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的创建>直接。 Parameter 类的 SqlParameter 属性可用于检索 SqlParameter

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

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

Is there any case in which the following structure is needed?

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

Or, should all cleanup tasks be performed in the implicit something.Dispose() call?


Here is the offending code:

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

NOTE: I have defined the Parameter class so users of this function don't have to deal with the creation of SqlParameters directly. The SqlParameter property of the Parameter class can be used to retrieve an SqlParameter.

At some point, my program does the following (cannot post the code, because it involves a lot of classes; basically, I have a mini-framework creating lots of objects):

  1. Create an array of Parameters.
  2. GetDataTable('sp_one', parameters).
  3. GetDataTable('sp_two', parameters).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

内心激荡 2024-11-04 19:40:44

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

  1. 此时,您可能会争辩说您最好跳过 using 块并在 finally 块内也调用 Dispose() 。就我个人而言,我仍然会使用 using 块。 始终为您的 IDisposable 实例保留一个好习惯。
  2. 我谦虚地建议,如果您满足上述条件,则需要重新设计您的类以利用 IDisposable 模式。

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

关键点是.Net 框架为非托管资源保留了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;
}

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

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

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 的代码是更好的选择。

在您的情况下,我会使用现有的基于操作委托的模式并希望尽可能地重复使用一组重复的参数,那就是拥有一个真正的命名方法,该方法知道如何添加参数并与我的操作委托相匹配。然后我可以传入该方法,并重用所需的参数。

The using keyword only calls the .Dispose() method. If you have necessary clean up that happens outside of the dispose method on an IDisposable object, then you will need to do that in it's own finally block. This bring up two points:

  1. At this point, you could argue that you might as well skip the using block and just call Dispose() inside the finally block, too. Personally, I'd still go with a using block. It's just a good habit to be in to always have one for your IDisposable instances.
  2. I humbly suggest that if you meed the conditions above, you need to re-design your class to take advantage of the IDisposable pattern.

Based on the code you posted, the problem is that your parameters are still rooted somewhere (perhaps you're re-using them?). Because the parameters are still rooted they cannot be collected. They also contain a reference to the command they are attached to, so your SqlCommand object cannot be collected right away either, because now it's still rooted as well.

The key point is that the .Net framework reserves the Dispose() pattern for unamanaged resources. Because SqlParameters and SqlParameterCollection are managed types, they are not touched until they are collected, which happens completely separately from disposal. When your SqlCommand is finally collected, it's SqlParameter collection will be taken care of as well. Just don't confuse collection, disposal, and their purposes.

What you want to do is make a copy of each parameter when you add it, rather than add the existing parameter to the collection.

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

Some things to note here: I was able to get rid of all your try blocks. Not one of them was needed. Also, the SqlDataAdapter.Fill() method will open and close the connection for you, so you don't need that part.

Now let's talk about that CloneParameter() function. I get the impression you feel like it defeats the purpose of your code, which is to try to re-use parameters. I promise you that parameter re-use here is a bad idea. The performance penalty is negligible to the point of non-existence, especially compared to the storedprocedure execution. I left the CloneParameter() implementation to you, for two reason: first of all it's trivial, and second is that we're already outside of my normal data access pattern. What I normally do to add parameters is accept an Action<SqlParameterCollection> delegate, rather than a parameter enumerable. The function is declared more like this:

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

and is called like this:

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

Since you're filling two tables in a row, it sounds like you have some work to do client side that may justify that vs the DataReader/IEnumerable approach, but I do want to mention this, as most of the time basing your code on a DataReader is the better option.

What I would do in your case with my existing Action-delegate-based pattern and wanting to re-use a duplicate set of parameters as much as possible is have a real, named method that knows how to add the parameters and matches my Action delegate. Then I could just pass that method in, and get the desired parameter re-use.

小ぇ时光︴ 2024-11-04 19:40:44

有趣的问题!

这完全取决于您的 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());

Interesting question!

It all depends on your Something class. If it was poorly designed and it needs multi-stage cleanup, it forces its idiosyncracies to its clients.

You shouldn't design classes to be like that. If you have interim cleanups to do, encapsulate them in their own classes and use code like this:

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

UPDATE:

For your example, it might look like this:

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

  }

}

UPDATE 2:

Just do this:

GetDataTable('sp_one', CreateParameters());
GetDataTable('sp_two', CreateParameters());
早乙女 2024-11-04 19:40:44

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

Dispose should clean up all your unmanaged resources. Having another cleanup method, to perform some functional or database actions for example, is perfectly possible.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文