前几天在代码审查期间出现了一个问题,即关闭使用块的速度有多快。一个阵营说,“一旦你完成了这个对象”;另一方面,“在它超出范围之前的某个时间”。
在此特定示例中,有一个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可能没问题”。我的意思是对处理所有类型的对象进行一般性的讨论,但我选择了一个不好的例子来说明它。由于大部分讨论都集中在该特定示例上,因此我选择了答案,该答案为我提供了有关所使用的特定对象的重要信息,并证明在做出此类决策时我需要仔细考虑每个对象。 (无论如何,选择一个问题的“最佳答案”很难像我的标题中那样含糊不清。)未来读者遇到同样的困境,请看下面的所有答案,因为他们中的许多人提出了有趣的观点。
答案 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循环才需要执行一段时间,在这种情况下,数据供应实体可能需要保持打开足够长的时间才能完成循环。