ADO.NET 是否过度使用 IDisposable?
Does ADO.NET go overboard with IDisposable?
当 .NET 首次出现时,我是许多抱怨 .NET 缺乏确定性终结(class 析构函数按不可预测的时间表调用)的人之一。微软当时做出的妥协是 using
声明。
虽然不完美,但我认为使用using
对于确保及时清理非托管资源很重要。
但是,我正在编写一些 ADO.NET 代码并注意到几乎每个 class 都实现了 IDisposable
。这导致代码看起来像这样。
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(query, connection))
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
using (SqlCommandBuilder builder = new SqlCommandBuilder(adapter))
using (DataSet dataset = new DataSet())
{
command.Parameters.AddWithValue("@FirstValue", 2);
command.Parameters.AddWithValue("@SecondValue", 3);
adapter.Fill(dataset);
DataTable table = dataset.Tables[0];
foreach (DataRow row in table.Rows) // search whole table
{
if ((int)row["Id"] == 4)
{
row["Value2"] = 12345;
}
else if ((int)row["Id"] == 5)
{
row.Delete();
}
}
adapter.Update(table);
}
我强烈怀疑我不需要所有这些using
声明。但是如果不详细了解每个 class 的代码,就很难确定我可以省略哪些。结果有点难看,并且有损于我代码中的主要逻辑。
有谁知道为什么所有这些 classes 都需要实施 IDisposable
? (Microsoft 有许多 code examples online 无需担心处理其中许多对象。)其他开发人员是否为所有这些对象编写 using
语句?而且,如果没有,您如何决定哪些可以没有?
如果 class 实现了 IDisposable
,那么您应该始终确保它得到释放,而不对其实现做任何假设。
我认为这是您可以编写的确保您的对象将被释放的最少代码量。我认为它绝对没有问题,它根本不会分散我对代码主要逻辑的注意力。
如果减少此代码对您来说至关重要,那么您可以想出自己的 SqlConnection
替代(包装器),其子 classes 未实现 IDisposable
而是在连接关闭后被连接自动销毁。但是,这将是一项巨大的工作量,并且您会失去一些关于何时处置某些内容的精确度。
这里的部分问题是 ADO.NET 是一个抽象的提供者模型。我们 不知道 在处理方面需要什么特定的实现(特定的 ADO.NET 提供者)。当然,我们可以合理地假设需要处理连接和事务,但是命令呢?可能是。 Reader?可能,尤其是因为 command-flags 选项之一允许您将连接的生命周期与 reader 相关联(因此连接在 reader 关闭时关闭,这在逻辑上应该扩展到处置)。
总的来说,我觉得应该还可以。
大多数时候,人们不会手动弄乱 ADO.NET,任何 ORM 工具(或 micro-ORM 工具,例如 "Dapper")都可以做到这一点 为你不用你担心。
我会公开承认,在我使用 DataTable
的少数情况下(说真的,现在是 2018 年——除了一些小众场景外,这不应该是你表示数据的默认模型):我没有处理它们。那有点没有意义:)
是的。 ADO.Net 中的几乎所有内容都在实现 IDisposable
,无论是否确实需要 - 例如 SqlConnection
(because of connection pooling) or not - in case of, say, DataTable
(Should I Dispose() DataSet and DataTable?
).
问题是,如问题中所述:
without understanding the code for each class in some detail, it's hard to be certain which ones I can leave out.
我认为仅此一项就足以将所有内容保存在 using
语句中 - 一句话:封装。
用很多话来说:
对于您正在使用的每个 class 的实现,没有必要非常熟悉,甚至非常熟悉。您只需要知道表面积 - 即 public 方法、属性、事件、索引器(和字段,如果 class 有 public 字段)。从 class 用户的角度来看,public 表面积以外的任何东西都是实现细节。
关于代码中的所有 using
语句 - 您可以通过创建一个接受 SQL 语句、Action<DataSet>
和参数数组的方法只编写一次它们参数。像这样:
void DoStuffWithDataTable(string query, Action<DataTable> action, params SqlParameter[] parameters)
{
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(query, connection))
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
using (SqlCommandBuilder builder = new SqlCommandBuilder(adapter))
using (var table = new DataTable())
{
foreach(var param in parameters)
{
command.Parameters.Add(param);
}
// SqlDataAdapter has a fill overload that only needs a data table
adapter.Fill(table);
action();
adapter.Update(table);
}
}
你就这样使用它,用于你需要对数据执行的所有操作 table:
DoStuffWithDataTable(
"Select...",
table =>
{ // of course, that doesn't have to be a lambda expression here...
foreach (DataRow row in table.Rows) // search whole table
{
if ((int)row["Id"] == 4)
{
row["Value2"] = 12345;
}
else if ((int)row["Id"] == 5)
{
row.Delete();
}
}
},
new SqlParameter[]
{
new SqlParameter("@FirstValue", 2),
new SqlParameter("@SecondValue", 3)
}
);
这样你的代码在处理任何 IDisposable
方面是 "safe",而你只为此编写了一次管道代码。
当 .NET 首次出现时,我是许多抱怨 .NET 缺乏确定性终结(class 析构函数按不可预测的时间表调用)的人之一。微软当时做出的妥协是 using
声明。
虽然不完美,但我认为使用using
对于确保及时清理非托管资源很重要。
但是,我正在编写一些 ADO.NET 代码并注意到几乎每个 class 都实现了 IDisposable
。这导致代码看起来像这样。
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(query, connection))
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
using (SqlCommandBuilder builder = new SqlCommandBuilder(adapter))
using (DataSet dataset = new DataSet())
{
command.Parameters.AddWithValue("@FirstValue", 2);
command.Parameters.AddWithValue("@SecondValue", 3);
adapter.Fill(dataset);
DataTable table = dataset.Tables[0];
foreach (DataRow row in table.Rows) // search whole table
{
if ((int)row["Id"] == 4)
{
row["Value2"] = 12345;
}
else if ((int)row["Id"] == 5)
{
row.Delete();
}
}
adapter.Update(table);
}
我强烈怀疑我不需要所有这些using
声明。但是如果不详细了解每个 class 的代码,就很难确定我可以省略哪些。结果有点难看,并且有损于我代码中的主要逻辑。
有谁知道为什么所有这些 classes 都需要实施 IDisposable
? (Microsoft 有许多 code examples online 无需担心处理其中许多对象。)其他开发人员是否为所有这些对象编写 using
语句?而且,如果没有,您如何决定哪些可以没有?
如果 class 实现了 IDisposable
,那么您应该始终确保它得到释放,而不对其实现做任何假设。
我认为这是您可以编写的确保您的对象将被释放的最少代码量。我认为它绝对没有问题,它根本不会分散我对代码主要逻辑的注意力。
如果减少此代码对您来说至关重要,那么您可以想出自己的 SqlConnection
替代(包装器),其子 classes 未实现 IDisposable
而是在连接关闭后被连接自动销毁。但是,这将是一项巨大的工作量,并且您会失去一些关于何时处置某些内容的精确度。
这里的部分问题是 ADO.NET 是一个抽象的提供者模型。我们 不知道 在处理方面需要什么特定的实现(特定的 ADO.NET 提供者)。当然,我们可以合理地假设需要处理连接和事务,但是命令呢?可能是。 Reader?可能,尤其是因为 command-flags 选项之一允许您将连接的生命周期与 reader 相关联(因此连接在 reader 关闭时关闭,这在逻辑上应该扩展到处置)。
总的来说,我觉得应该还可以。
大多数时候,人们不会手动弄乱 ADO.NET,任何 ORM 工具(或 micro-ORM 工具,例如 "Dapper")都可以做到这一点 为你不用你担心。
我会公开承认,在我使用 DataTable
的少数情况下(说真的,现在是 2018 年——除了一些小众场景外,这不应该是你表示数据的默认模型):我没有处理它们。那有点没有意义:)
是的。 ADO.Net 中的几乎所有内容都在实现 IDisposable
,无论是否确实需要 - 例如 SqlConnection
(because of connection pooling) or not - in case of, say, DataTable
(Should I Dispose() DataSet and DataTable?
).
问题是,如问题中所述:
without understanding the code for each class in some detail, it's hard to be certain which ones I can leave out.
我认为仅此一项就足以将所有内容保存在 using
语句中 - 一句话:封装。
用很多话来说: 对于您正在使用的每个 class 的实现,没有必要非常熟悉,甚至非常熟悉。您只需要知道表面积 - 即 public 方法、属性、事件、索引器(和字段,如果 class 有 public 字段)。从 class 用户的角度来看,public 表面积以外的任何东西都是实现细节。
关于代码中的所有 using
语句 - 您可以通过创建一个接受 SQL 语句、Action<DataSet>
和参数数组的方法只编写一次它们参数。像这样:
void DoStuffWithDataTable(string query, Action<DataTable> action, params SqlParameter[] parameters)
{
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(query, connection))
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
using (SqlCommandBuilder builder = new SqlCommandBuilder(adapter))
using (var table = new DataTable())
{
foreach(var param in parameters)
{
command.Parameters.Add(param);
}
// SqlDataAdapter has a fill overload that only needs a data table
adapter.Fill(table);
action();
adapter.Update(table);
}
}
你就这样使用它,用于你需要对数据执行的所有操作 table:
DoStuffWithDataTable(
"Select...",
table =>
{ // of course, that doesn't have to be a lambda expression here...
foreach (DataRow row in table.Rows) // search whole table
{
if ((int)row["Id"] == 4)
{
row["Value2"] = 12345;
}
else if ((int)row["Id"] == 5)
{
row.Delete();
}
}
},
new SqlParameter[]
{
new SqlParameter("@FirstValue", 2),
new SqlParameter("@SecondValue", 3)
}
);
这样你的代码在处理任何 IDisposable
方面是 "safe",而你只为此编写了一次管道代码。