在这种情况下,你根本不应该实现IDisposable,或者如果它存在是因为它被认为很可能在未来很有必要,那么它就会有一个空的实现。你当然不应该在那里有一个终结者。除非您 100% 确定确实需要一台,否则永远不要拥有一台。
在某些情况下,您希望实现 IDisposable,在某些情况下,您还希望拥有一个析构函数(这是 C# 拥有终结器的方式)。
其中一个是当对象完成时您有一些非常重要的事情要做,通常会撤消您之前所做的事情,例如释放您获得的句柄,关闭您打开的连接等,但不是托管内存。 (所有对象都使用托管内存,并且所有对象都为它们清理托管内存,如果它们不能再次使用并且其他东西需要更多托管内存,这就是 managed 中的“托管内存”是指)。
public class SomeClass : IDisposable
{
private IntPtr _someHandle;
public SomeClass(string someIdentifier)
{
_someHandle = GetHandle(someIdentifier);
}
public void Dispose()
{
ReleaseHandle(_someHandle);
}
}
所以现在,每当使用 SomeClass 的东西用它完成时,它就会在其上调用 Dispose()(可能通过 using 块隐式地调用)并且一切都被很好地清理了。
但如果这没有发生呢?好吧,这就是为什么我们可能会有一个终结者:
public class SomeClass : IDisposable
{
private IntPtr _someHandle;
public SomeClass(string someIdentifier)
{
_someHandle = GetHandle(someIdentifier);
}
public void Dispose()
{
ReleaseHandle(_someHandle);
_someHandle = null; // so we know not to release twice.
}
~SomeClass()
{
if(_someHandle != null)
ReleaseHandle(_someHandle);
}
}
所以,如果Dispose() 没有被调用,我们仍然会进行清理,因为正常的垃圾收集过程:
- 意识到您需要更多内存。
- 查找不再使用的对象。
- 回收这些对象的内存。
添加了以下步骤:
- 实现您要回收其内存的对象有一个要运行的终结器。
- 将该对象放入其他此类对象的队列中。
- (在单独的线程上)运行对象的终结器。
- 该对象不再是按照上述第 4 步“需要运行终结器”的对象,因此下次可以回收它。
所有这些都有缺点:
- 我们无法保证何时会发生这种情况。
- 我们没有在第 3 步中回收尽可能多的内存,因为有这样一个对象。
- 垃圾收集是分代的,与对象的分代收集相得益彰意味着要么很快死去,要么活得很长,在 GC 第一次尝试收集对象之后死去几乎是最不理想的时间。
我们可以通过调用Dispose() 来绕过前两个,而不是让终结发生,这取决于类的用户,而不是类本身。我们通过让一个知道它不需要最终确定的对象来解决第三个问题,将自己标记为不再需要:
public class SomeClass : IDisposable
{
private IntPtr _someHandle;
public SomeClass(string someIdentifier)
{
_someHandle = GetHandle(someIdentifier);
}
public void Dispose()
{
ReleaseHandle(_someHandle);
GC.SuppressFinalize(this);
}
~SomeClass()
{
ReleaseHandle(_someHandle);
}
}
如果已将对象传递给GC.SuppressFinalize(),则不会发生第 4 步及后续步骤。
第二种情况,你可能会实现IDisposable 是你有一个IDisposable 对象作为另一个“拥有”它的对象的字段(控制它的生命周期):
public class SomeOtherClass : IDisposable
{
private SomeClass _someObj;
public SomeOtherClass(string someIdentifier)
{
_someObj = new SomeClass(someIdentifier);
}
public void Dispose()
{
//If base type is disposable
//call `base.Dispose()` here too.
_someObj.Dispose();
}
}
清理SomeOtherClass 因此意味着清理它作为字段的SomeClass。请注意,我们这里没有有一个终结器。我们不需要终结器,因为它无关紧要;充其量它什么也不做,只有上面提到的终结者的缺点,更糟糕的是它会尝试清理_someObj而不知道这是否会在_someObj清理之前或之后发生,并且_someObj排队清理以一种可以假设没有其他任何东西可以进行清理的方式自行清理。
对于第三种情况,考虑我们是否将这两种情况与一个具有both它释放的非托管资源和一个一次性类的字段的类结合起来。在这里,如果我们是Dispose()d,我们想清理两者,但如果我们最终确定,我们只想清理直接处理的非托管资源:
public sealed class SomeHybridClass : IDisposable
{
private IntPtr _someHandle;
private SomeClass _someObj;
public SomeHybridClass(string someIdentifier)
{
_someHandle = GetHandle(someIdentifier);
_someObj = new SomeClass(someIdentifier);
}
public void Dispose()
{
ReleaseHandle(_someHandle);
GC.SuppressFinalize(this);
_someObj.Dispose();
}
~SomeHybridClass()
{
ReleaseHandle(_someHandle);
}
}
现在,由于这里有重复,因此将它们重构为相同的方法是有意义的:
public sealed class SomeHybridClass : IDisposable
{
private IntPtr _someHandle;
private SomeClass _someObj;
public SomeHybridClass(string someIdentifier)
{
_someHandle = GetHandle(someIdentifier);
_someObj = new SomeClass(someIdentifier);
}
private void Dispose(bool disposing)
{
if(disposing)
{
_someObj.Dispose();
}
ReleaseHandle(_someHandle);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~SomeHybridClass()
{
Dispose(false);
}
}
对于第四种情况,想象一下如果这个类不是密封的;它的派生类型也需要能够进行这种清理,因此我们将参数化的Dispose(bool) 方法设为保护:
public class SomeHybridClass : IDisposable
{
private IntPtr _someHandle;
private SomeClass _someObj;
public SomeHybridClass(string someIdentifier)
{
_someHandle = GetHandle(someIdentifier);
_someObj = new SomeClass(someIdentifier);
}
protected virtual void Dispose(bool disposing)
{
// if this in turn was derived, we'd call
// base.Dispose(disposing) here too.
if(disposing)
{
_someObj.Dispose();
}
ReleaseHandle(_someHandle);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~SomeHybridClass()
{
Dispose(false);
}
}
但是,最后这两个示例确实解决了错误的问题:它们解决了如何让类既具有一次性类型作为字段又具有非托管资源和/或成为类型的一部分的问题发生这种情况的层次结构。真的,你最好不要陷入这种情况;要么有一个仅处理非托管资源的类(并且是sealed)或在字段中有一次性类型,你最终只能处理前两种情况。如果您通过从SafeHandle 派生来处理非托管资源,那么您实际上只需要担心第二种情况,而且它也可以管理一些困难的边缘情况。
确实,终结器应该非常、非常少地编写,并且在编写它们时应该尽可能简单,因为它们本身就有足够的复杂性以及它们周围的边缘情况。您需要知道如何处理覆盖protected virtual void Dispose(bool disposing)(注意,永远不应该公开)来处理对某人来说似乎是个好主意但没有可继承类同时具有非托管和托管一次性资源的遗留问题强迫别人进入那个位置。
如何在 Dispose 方法中释放 Base 的属性? (_String, classe1, foo)
现在应该清楚了,这些字段(属性在 .NET 中是完全不同的东西)不需要被释放。他们拥有的唯一资源是托管内存,因此一旦无法访问它们(不在静态中,不打算在方法中对它们做某事,也不在某个领域在这些类别中的任何一个或其中任何一个的字段中的某物的字段中,等等)他们的内存将在需要时自动回收。