在没有资源的类上实现IDisposable有什么好处吗
本文关键字:什么 IDisposable 实现 资源 | 更新日期: 2023-09-27 18:26:01
在C#中,如果一个类(如管理器类)没有资源,那么使用: IDisposable
有什么好处吗?
简单示例:
public interface IBoxManager
{
int addBox(Box b);
}
public class BoxManager : IBoxManager
{
public int addBox(Box b)
{
using(dataContext db = new dataContext()){
db.Boxes.add(b);
db.SaveChanges();
}
return b.id;
}
}
如果BoxManager也实现IDisposable,那么在使用它时,内存使用会有什么好处吗?public class BoxManager : IBoxManager , IDisposable
例如:
BoxManager bm = new BoxManager();
bm.add(myBox);
bm.dispose();//is there benefit to doing this?
在类型上实现IDisposable
只有两个原因
- 该类型包含在不再使用该类型时必须释放的本机资源
- 类型包含类型为
IDisposable
的字段
如果这两者都不成立,那么就不要实现IDisposable
编辑
一些人已经提到IDisposable
是实现开始/结束或预定结束操作的好方法。虽然这不是IDisposable
的初衷,但它确实提供了一个非常好的模式。
class Operation {
class Helper : IDisposable {
internal Operation Operation;
public void Dispose() {
Operation.EndOperation();
}
}
public IDisposable BeginOperation() {
...
return new Helper() { Operation = this };
}
private void EndOperation() {
...
}
}
注意:实现此模式的另一种有趣的方法是使用lambdas。与其给用户一个IDisposable
并希望他们不要忘记调用Dispose
,不如让他们给你一个lambda,在这个lambda中他们可以执行操作,然后你关闭操作
public void BeginOperation(Action action) {
BeginOperationCore();
try {
action();
} finally {
EndOperation();
}
}
虽然您的代码不会从实现IDisposable中受益,但我不同意这里的其他观点,即IDisposaable只意味着(直接或间接)释放本地资源。只要对象在其生命周期结束时需要执行清理任务,就可以使用IDisposable。它与using
配合使用非常有用。
一个非常流行的例子:在ASP.NET MVC中,Html.BeginForm
返回一个IDisposable。创建时,对象打开标记,调用Dispose时关闭标记。不涉及本机资源,但仍然很好地利用了IDisposable。
如果不明确使用Dispose()
方法,一次性和非一次性版本之间不会有任何区别。
不,如果不做一些有用的事情,比如释放类可能在Dispose
方法中持有的非托管资源,那就没有任何好处。
一个主要的混淆点可能不适用于您的情况,但经常出现,那就是什么才是真正的"资源"。从对象的角度来看,非托管资源是外部实体()代表其"做"(*)的事情,该外部实体将继续做这件事——损害其他实体——直到被告知停止。例如,如果一个对象打开了一个文件,则承载该文件的机器可能会授予该对象独占访问权限,从而拒绝宇宙中的其他人使用它的机会,除非或直到它收到不再需要独占访问的通知。
(*)可以是任何东西,任何地方;甚至可能不在同一台计算机上。
(**)或以某种方式改变外部实体的行为或状态的
如果一个外部实体正在代表一个被放弃和消失的对象做一些事情,而没有首先让该实体知道不再需要它的服务,那么该外部实体将无法知道它应该停止代表不再存在的对象行事。IDisposable提供了一种避免此问题的方法,即当不需要对象的服务时提供通知对象的标准方法。不再需要服务的对象通常不需要向任何其他实体请求任何进一步的帮助,因此可以请求任何代表其行事的实体停止这样做
为了允许在没有首先调用IDisposable.Dispose()
的情况下放弃对象,系统允许对象注册一个名为Finalize()
的"故障保护"清理方法。因为无论出于何种原因,C#的创建者都不喜欢Finalize()
这个术语,所以该语言需要使用一个名为"析构函数"的构造,它可以做同样的事情。请注意,通常情况下,Finalize()
会掩盖而不是解决问题,并且可能会产生自己的问题,因此在使用时应格外小心。
"托管资源"通常是给实现IDisposable
的对象的名称,通常(尽管不总是)实现终结器。
否,如果没有(托管或非托管)资源,也不需要IDisposable
。
小警告:有些人使用IDisposable来清理事件处理程序或大内存缓冲区,但
- 你好像不用那些
- 无论如何,这是一个值得怀疑的模式
根据我的个人经验(在这里的讨论和其他帖子中得到了证实),我想说,在某些情况下,您的对象可能使用了大量的事件,或者不是大量的,而是频繁地订阅和取消订阅事件,这有时会导致对象没有被垃圾收集。在这种情况下,Dispose
中的I取消订阅我的对象之前订阅的所有事件。
希望这能有所帮助。
IDisposable
如果您想从using () {}
语法中获益,它也很好。
在一个使用ViewModels的WPF项目中,我希望能够暂时禁止引发NotifyPropertyChange
事件。为了确保其他开发人员会重新启用通知,我写了一些代码,以便能够编写以下内容:
using (this.PreventNotifyPropertyChanges()) {
// in this block NotifyPropertyChanged won't be called when changing a property value
}
语法看起来不错,而且很容易阅读。要使其工作,需要编写一些代码。你需要一个简单的一次性物品和计数器。
public class MyViewModel {
private volatile int notifyPropertylocks = 0; // number of locks
protected void NotifyPropertyChanged(string propertyName) {
if (this.notifyPropertylocks == 0) { // check the counter
this.NotifyPropertyChanged(...);
}
}
protected IDisposable PreventNotifyPropertyChanges() {
return new PropertyChangeLock(this);
}
public class PropertyChangeLock : IDisposable {
MyViewModel vm;
// creating this object will increment the lock counter
public PropertyChangeLock(MyViewModel vm) {
this.vm = vm;
this.vm.notifyPropertylocks += 1;
}
// disposing this object will decrement the lock counter
public void Dispose() {
if (this.vm != null) {
this.vm.notifyPropertylocks -= 1;
this.vm = null;
}
}
}
}
这里没有可处置的资源。我想要一个干净的代码,带有一种try/finaly语法。using关键字看起来更好。
拥有它有什么好处吗:IDisposable?
然而,在您的特定示例中,情况并非如此:即使您没有任何IDisposable
字段,也有一个很好的理由来实现IDisposable
:您的后代可能会。
这是IDisposable中强调的IDisposable
的一个重大体系结构问题:关于资源分配,你母亲从未告诉过你什么。基本上,除非你的类被封存,否则你需要决定你的后代是否可能有IDisposable
成员。这不是你可以实际预测的。
因此,如果您的后代可能具有IDisposable
成员,那么这可能是在基类中实现IDisposable
的一个很好的理由。
简单的答案是否定的。但是,您可以在对象生命周期结束时巧妙地使用Dispose()
执行的性质。已经给出了一个很好的MVC示例(Html.BeginForm
)
我想指出IDisposable
和using() {}
声明的一个重要方面。在Using
语句的末尾,会在using上下文对象上自动调用Dispose()
方法(当然,它必须实现了IDisposable
接口)。
还有一个原因没有人提到(尽管这是否真的值得商榷):约定说,如果有人使用实现IDisposable
的类,则必须调用其Dispose方法(显式或通过"using"语句)。但是,如果类的V1(在您的公共API中)不需要IDisposable,而V2需要IDispasable,会发生什么?从技术上讲,向类添加接口不会破坏向后兼容性,但由于的惯例,确实如此!因为代码的旧客户端不会调用其Dispose方法,并且可能导致资源无法释放。避免这种情况的(几乎)唯一方法是在您怀疑将来需要IDisposable的任何情况下实现它,以确保您的客户端始终调用您的Dispose方法,这可能有一天真的需要。另一种(可能更好的)方法是在API的V2中实现JaredPar上面提到的lambda模式。