关于使用“使用”和“最后”清理资源
是否存在需要以下结构的情况?
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
。
在某些时候,我的程序会执行以下操作(无法发布代码,因为它涉及很多类;基本上,我有一个创建大量对象的迷你框架):
- 创建一个
Parameter
数组。 GetDataTable('sp_one', 参数)
。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 SqlParameter
s 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):
- Create an array of
Parameter
s. GetDataTable('sp_one', parameters)
.GetDataTable('sp_two', parameters)
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
using
关键字仅调用.Dispose()
方法。如果您需要在 IDisposable 对象的 dispose 方法之外进行必要的清理工作,那么您将需要在它自己的finally 块中执行此操作。这提出了两点:using
块。 始终为您的 IDisposable 实例保留一个好习惯。根据您发布的代码,问题是您的参数仍然植根于某个地方(也许您正在重复使用它们?)。因为参数仍然是 root,所以无法收集。它们还包含对它们所附加的命令的引用,因此您的 SqlCommand 对象也无法立即收集,因为现在它仍然是 root 的。
关键点是.Net 框架为非托管资源保留了Dispose() 模式。由于 SqlParameters 和 SqlParameterCollection 是托管类型,因此在收集它们之前不会触及它们,而收集过程与处置完全分开。当您的 SqlCommand 最终被收集时,它的 SqlParameter 收集也将被处理。只是不要混淆收集、处置及其目的。
您想要做的是在添加每个参数时为其创建一个副本,而不是将现有参数添加到集合中。
这里需要注意一些事情:我能够摆脱所有你的 try 块。其中一个都不需要。另外,SqlDataAdapter.Fill() 方法将为您打开和关闭连接,因此您不需要该部分。
现在我们来谈谈 CloneParameter() 函数。我的印象是,您觉得它违背了代码的目的,即尝试重用参数。我向你保证,在这里重复使用参数是一个坏主意。性能损失可以忽略不计,甚至不存在,特别是与存储过程执行相比。我将 CloneParameter() 实现留给您,有两个原因:首先它很简单,其次我们已经超出了正常的数据访问模式。我通常添加参数的做法是接受 Action委托,而不是可枚举的参数。该函数的声明方式更像是这样的:
并这样调用:
由于您要连续填充两个表,听起来您需要在客户端做一些工作,这可能会证明与 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:using
block. It's just a good habit to be in to always have one for your IDisposable instances.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.
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:
and is called like this:
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.
有趣的问题!
这完全取决于您的
Something
类。如果它设计得不好并且需要多阶段清理,就会将其特性强加给客户。你不应该将类设计成那样。如果您需要进行临时清理,请将它们封装在自己的类中并使用如下代码:
更新:
对于您的示例,它可能如下所示:
更新 2:
只是这样做:
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:
UPDATE:
For your example, it might look like this:
UPDATE 2:
Just do this:
处置应该清理所有非托管资源。使用另一种清理方法(例如执行某些功能或数据库操作)是完全可能的。
Dispose should clean up all your unmanaged resources. Having another cleanup method, to perform some functional or database actions for example, is perfectly possible.