我应该多快关闭一个使用区块?

时间:2011-03-31 02:46:35

标签: c# .net performance idisposable using-statement

前几天在代码审查期间出现了一个问题,即关闭使用块的速度有多快。一个阵营说,“一旦你完成了这个对象”;另一方面,“在它超出范围之前的某个时间”。

在此特定示例中,有一个DataTable和一个SqlCommand对象要处理。我们需要在一个语句中引用它们,我们需要迭代DataTable。

Camp 1:

List<MyObject> listToReturn = new List<MyObject>();
DataTable dt = null;
try
{
    using (InHouseDataAdapter inHouseDataAdapter = new InHouseDataAdapter())
    using (SqlCommand cmd = new SqlCommand())
    {
        dt = inHouseDataAdapter.GetDataTable(cmd);
    }

    foreach (DataRow dr in dt.Rows)
    {
        listToReturn.Add(new MyObject(dr));
    }
}
finally
{
    if (dt != null)
    {
        dt.Dispose();
    }
}

推理:完成后立即处理SqlCommand。不要在另一个对象的使用块内启动可能很长的操作,例如迭代表。

Camp 2:

List<MyObject> listToReturn = new List<MyObject>();
using (InHouseDataAdapter inHouseDataAdapter = new InHouseDataAdapter())
using (SqlCommand cmd = new SqlCommand())
using (DataTable dt = inHouseDataAdapter.GetDataTable(cmd))
{
    foreach (DataRow dr in dt.Rows)
    {
        listToReturn.Add(new MyObject(dr));
    }
}

推理:此代码更清晰。无论如何都保证所有物品都被丢弃,并且没有一个物品真的是资源密集型的,所以立即处置它们并不重要。

我在2号营地。你们在哪里,为什么?

编辑:有几个人指出DataTable不需要处理(参见Corey Sunwold's answer),而Camp 1的原始示例比它需要的更为丑陋。这里有一些修改过的例子,也考虑到大多数时候,我必须在SqlCommand上设置一些属性这一事实。如果有人已经看到或者可以想出一个更好的例子来支持这两个职位,请分享。

Camp 1,版本2:

DataTable dt = null;
using (InHouseDataAdapter inHouseDataAdapter = new InHouseDataAdapter(_connectionString))
using (SqlCommand cmd = new SqlCommand("up_my_proc"))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@class_id", 27);
    dt = inHouseDataAdapter.GetDataTable(cmd);
}

foreach (DataRow dr in dt.Rows)
{
    listToReturn.Add(new MyObject(dr));
}

Camp 2,版本2:

using (InHouseDataAdapter inHouseDataAdapter = new InHouseDataAdapter(_connectionString))
using (SqlCommand cmd = new SqlCommand("up_my_proc"))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@class_id", 27);
    DataTable dt = inHouseDataAdapter.GetDataTable(cmd);
    foreach (DataRow dr in dt.Rows)
    {
        listToReturn.Add(new MyObject(dr));
    }
}

我认为大多数人会同意可读性论证现在大大减少了,而这并不是我想要问的最好的例子。 (一旦我告诉你在GetDataTable()方法退出之前关闭了SqlConnection,并且在这个实例中使用的数据没有可衡量的性能差异,那就更是如此了。)如果我可以在这里添加我的问题,那么我是否立即处置对象确实有所作为的实例?例如,as Gregory Higley mentioned,一个像OS句柄一样的共享资源。

编辑(解释我的答案选择)非常感谢所有提供意见,示例和其他有用反馈的人!我们似乎是平分秋色,但是从每个人的答案中脱颖而出的是“阵营1绝对正确,但取决于对象,阵营2可能没问题”。我的意思是对处理所有类型的对象进行一般性的讨论,但我选择了一个不好的例子来说明它。由于大部分讨论都集中在该特定示例上,因此我选择了答案,该答案为我提供了有关所使用的特定对象的重要信息,并证明在做出此类决策时我需要仔细考虑每个对象。 (无论如何,选择一个问题的“最佳答案”很难像我的标题中那样含糊不清。)未来读者遇到同样的困境,请看下面的所有答案,因为他们中的许多人提出了有趣的观点。

9 个答案:

答案 0 :(得分:3)

大多数情况下我同意训练营1.但是,你应该注意它not necessary to dispose a DataTable

答案 1 :(得分:1)

我认为这取决于一次性物品持有何种非托管资源。总的来说,我在2号营地和你在一起。

如果以某种方式共享非托管资源,特别是如果它代表某种操作系统句柄,我会尝试尽快处理它。

答案 2 :(得分:0)

我和你一样在2号营地。有时资源的关键性由机器上的可用资源决定。 Camp 2解决方案确实相信预防措施,在您完成时移除物体而不是懒惰的Camp 1。

答案 3 :(得分:0)

我们通常使用camp 2 - 因为外部using语句使用SqlTransaction对象。

我们需要在最后处理事务,因此如果在使用数据读取器时抛出异常,则可以回滚事务。

答案 4 :(得分:0)

Camp2更具可读性,因此在大多数情况下我更喜欢Camp1。代码越可读,支持它的痛苦就越小。

在极少数情况下,如果明确需要非常快速地处理资源,我更倾向于Camp1。我的意思是,如果您稍后处理连接时遇到一些问题。但是在大多数情况下,如果你采用Camp2方式就不会受到任何惩罚。

答案 5 :(得分:0)

这一切都归结为您认为持有外部资源多久是合理的。数据连接不是免费的,因此尽快清理它们是有意义的。为了便于阅读,我仍然会保持自己的立场,因为这里的资源保留对象非常清楚。

但实际上这是一个最纯粹的问题,因为我们在这里谈论的是毫秒。我会在1号营地说错,特别是如果你长时间使用所查询的数据。

我也会摆脱DataTable的清理工作。在没有资源引用的托管对象上不需要它。而这样做几乎否定了可读性论点。

答案 6 :(得分:0)

+1 for Camp 2。

您的Camp 1样本处理可能在处理后使用的对象。我认为这不是你的确切方案的问题,但可能是其他情况的问题。 Camp 2版本强制您将对象置于正确嵌套的作用域中,使代码安全。

样品:

StreamWriter writer;
using(var stream = new FileStream(name))
{
    writer = new StreamWriter(stream);
}
writer.Write(1); // <= writnig to closed stream here.
writer.Dispose();

答案 7 :(得分:0)

你的Camp 1示例有点像稻草人,它不必像那样难看。

如果性能是一个问题(这可能是罕见的,而且在这个人为的例子中不太可能),我会通过将DataTable的生成重构为一种方法来寻找更简洁的“Camp 1”版本:

private DataTable GetDataTableFromInHouseAdapter()
{
    using (InHouseDataAdapter inHouseDataAdapter = new InHouseDataAdapter())
    using (SqlCommand cmd = new SqlCommand())
    {
        return inHouseDataAdapter.GetDataTable(cmd);
    }
}

...
List<MyObject> listToReturn = new List<MyObject>();
using (DataTable dt = GetDataTableFromInHouseAdapter())
{
    foreach (DataRow dr in dt.Rows)
    {
        listToReturn.Add(new MyObject(dr));
    }
}

这看起来更现实 - 生成DataTable的方法属于数据访问层,而转换为MyObject实例列表可能属于DAL上方的Facade。

事实上,我总是会考虑将嵌套的using语句重构为自己的方法 - 除非它们密切相关(例如SqlConnection / Command,甚至是InHouseDataAdapter / SqlCommand)。

答案 8 :(得分:0)

另一种可能的设计是在其“使用”块中关闭inHouseDataAdapter或SqlCommand,因为需要适当的IDisposable实现来容忍多次清理尝试。在许多情况下,我认为在Using块中显式关闭/处置可能是一个好主意,因为显式调用的Close方法可能提供比IDisposable.Dispose更有用的异常(针对IDisposable.Dispose抛出异常的参数不会适用于Close方法)。

在这个特定场景中,我肯定会在将DataTable复制到List时将SqlCommand和inHouseDataAdapter保持打开状态。如果GetDataTable返回实际保存数据的DataTable,则foreach循环应该快速执行(因此数据供应实体不会保持打开过长的时间)。只有当它返回一个惰性读取DataTable派生时,foreach循环才需要执行一段时间,在这种情况下,数据供应实体可能需要保持打开足够长的时间才能完成循环。