Dispose中的空“using”语句

23

最近我看到一些写成以下形式的代码:

public void Dipose()
{
   using(_myDisposableField) { }
}

对我来说,这似乎很奇怪,我更喜欢看到myDisposableField.Dispose();

使用“using”释放对象的原因是什么,明确调用与之相比有哪些好处?


15
看起来作者认为自己很聪明。 - David Heffernan
2
我看不出有任何理由这样做... - Chris
要么是缺少了一些代码,要么就需要更彻底地审查某人的代码,直到他们学会如何正确编写代码。 - Tony Hopkinson
2
没有理由给负评,我认为这很有趣,点赞。 - Adam Houldsworth
4个回答

24
No, 没有。它只会编译成一个空的try/finally块,并最终调用Dispose方法。

移除它。这将使代码更快、更易读,也可能更重要的是(随着下面的阅读),更能表达其意图。

更新:他们稍微聪明了一点,等效的代码需要进行空值检查,并且根据Jon Skeet的建议,在多线程的情况下也要进行本地拷贝(与标准事件调用模式相同,以避免在空值检查和方法调用之间发生竞态条件)。
IDisposable tmp = _myDisposableField; 

if (tmp != null) 
    tmp.Dispose();

根据我在编写示例应用程序的IL中看到的内容,似乎你还需要将_myDisposableField直接视为IDisposable。如果任何类型同时提供public void Dispose()方法并显式实现IDisposable接口,那么这点非常重要。

这段代码也没有尝试复制使用using时存在的try-finally,但可以认为这是不必要的。然而,正如Michael Graczyk在评论中指出的那样,使用finally可以提供保护来处理异常,特别是ThreadAbortException(可能在任何时候发生)。尽管如此,实际发生这种情况的机会非常小。

虽然我打赌他们这样做并没有真正理解它给他们带来了什么微妙的“好处”。


5
@JonSkeet 确实。但是无论哪种情况,共同点都是要狠批那些没有注释就这样做的开发人员。 - Adam Houldsworth
3
我不确定如何解析这个句子。你是要“毫不留情地打他们,因为他们做了这件事”,还是要打他们,“因为他们毫无评论”? :) - Jon Skeet
2
@Jon Both,他们应该被打两个耳光。正手和反手 :-P - Adam Houldsworth
1
@JonSkeet 发现将变量视为 IDisposable 也很重要,因为隐式和显式接口定义存在差异。 - Adam Houldsworth
1
@AdamHouldsworth 还有一个重要的区别。 OP发布的代码将在受保护的部分(在finally块内)中运行_myDisposableField.IDisposable.Dispose()。这意味着它不会被ThreadAbortException等中断。看起来这个“聪明”的代码越来越聪明了。 - Michael Graczyk
显示剩余8条评论

2

此答案所讨论的,这是避免空测试的聪明方式,但是:它可能不仅仅是这样。在现代C#中,在许多情况下,您可以使用空条件运算符实现类似的效果:

public void Dipose()
    => _myDisposableField?.Dispose();

然而,_myDisposableField 的类型在公共 API 中并不需要有 Dispose() 方法;它可以是:

public class Foo : IDisposable {
    void IDisposable.Dispose() {...}
}

或者更糟糕的是:

public class Bar : IDisposable {
    void IDisposable.Dispose() {...}
    public void Dispose() {...} // some completely different meaning! DO NOT DO THIS!
}

在第一种情况下,Dispose() 方法将找不到,而在第二种情况下,Dispose() 方法将调用错误的方法。在这两种情况下,using技巧都可以起作用,强制转换也可以(尽管如果它是值类型,则会做稍微不同的事情):
public void Dipose()
    => ((IDisposable)_myDisposableField)?.Dispose();

如果您不确定类型是否是可丢弃的(在某些多态情况下可能会发生),您也可以使用以下任一选项:

public void Dipose()
    => (_myDisposableField as IDisposable)?.Dispose();

或者:

public void Dipose()
{
    using (_myDisposableField as IDisposable) {}
}

我认为,当您有一个明确实现的接口和另一个同名方法时,无论您做什么,它都会调用错误的内容。 - Dave Hillier
@DaveHillier 为了增加趣味性,你可以加入以下代码:A : IDisposable { public void Dispose(); void IDisposable.Dispose(); }B : A, IDisposable { public new void Dispose(); void IDisposable.Dispose(); } :) - Marc Gravell

2

你贴出的示例中存在一个非常微妙但危险的bug。

虽然它编译成:

try {}
finally
{
    if (_myDisposableField != null) 
        ((IDisposable)_myDisposableField).Dispose();
}

对象应该在using语句内部实例化,而不是外部:

你可以实例化资源对象并将变量传递给using语句,但这不是最佳实践。在这种情况下,在控制离开using块之后,对象仍然在作用域内,但可能无法访问其非托管资源。换句话说,它不再完全初始化。如果你尝试在using块外部使用对象,则有可能引发异常。因此,最好在using语句中实例化对象并将其作用域限制为using块。

using语句 (C#参考)

换句话说,这是一种肮脏的方法。

干净的版本在MSDN上非常清楚地说明:

  • 如果可以将实例的使用限制在一个方法中,则使用带有构造函数调用的using块。不要直接使用Dispose
  • 如果需要(但确实需要)使实例保持活动状态直到父级被处理,则使用可处理模式显式处理。有不同的实现处理级联,但它们需要以相似的方式完成,以避免非常微妙和难以捕获的错误。MSDN上有一个非常好的资源在框架设计指南中。

最后,请注意,只有在使用非托管资源时才应该使用IDisposable模式。确保真正需要使用它 :-)


-2

using语句定义了引用对象应在其后的代码段完成后被释放的范围。

是的,你可以在结束时调用.dispose,但这会让对象的作用域变得不太清晰(依我的看法)。因人而异。


我没有点踩,但我猜可能是因为你的回答是关于using语句的一般情况,而不是这个特定情况,即OP试图用等效代码替换一个空的using语句。 - Adam Houldsworth
最后一行写道(我引用):“使用‘using’来处理对象的原因是什么,而不是显式地进行调用?”谁给踩了显然没有看清问题! - Robbie Dee
1
是的,但作用域在这里使用 using 的原因并不重要。作用域是类成员,使用 using 不会使任何内容更清晰。调用 Dispose 实际上会使它更清晰,因为 using 倾向于用于在同一作用域中声明、使用和处理的内容。 - Adam Houldsworth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接