IDisposable, Finalizer, and SuppressFinalize in C# and C++/CLI
转载至 https://manski.net/2012/01/idisposable-finalizer-and-suppressfinalize/
The .NET framework features an interface called IDisposable. It basically exists to allow freeing unmanaged resources (think: C++ pointers). In most cases, you won’t need IDisposable
when writing C# code. There are some exceptions though, and it becomes more important when writing C++/CLI code.
The help page for IDisposable
provides the code for IDisposable
‘s default implementation pattern in C#. This article will explain each part of it step by step and also provide the equivalent C++/CLI code in each step.
在.NET框架功能称为接口IDisposable接口。它的存在基本上是为了释放非托管资源(请考虑:C ++指针)。在大多数情况下,IDisposable
编写C#代码时不需要。虽然有一些例外,但是在编写C ++ / CLI代码时它变得更加重要。
在帮助页面的IDisposable
提供的代码IDisposable
在C#中的默认的实现模式。本文将逐步解释它的每个部分,并在每个步骤中提供等效的C ++ / CLI代码。
Summary – for the impatient 摘要
Here’s the summary of this article for those who don’t want to read the actual explanations.
Rules: 原则
- For a class owning managed resources, implement
IDisposable
(but not a finalizer). 仅有托管资源时,实现IDisposable接口 -
For a class owning at least one unmanaged resource, implement both
IDisposable
and a finalizer. 有非托管资源时,要同时实现IDisposable接口和析构函数(C#称为终结器)
C# code:
class DataContainer : IDisposable { public void Dispose() { Dipose(true); GC.SuppressFinalizer(this); } ~DataContainer() { // finalizer Dispose(false); } protected virtual void Dispose(bool disposing) { if (m_isDisposed) return; if (disposing) { // Dispose managed data //m_managedData.Dispose(); } // Free unmanaged data //DataProvider.DeleteUnmanagedData(m_unmanagedData); m_isDisposed = true; } private bool m_disposed = false; }
C++/CLI code:
ref class DataContainer { public: ~DataContainer() { if (m_isDisposed) return; // dispose managed data //delete m_managedData; this->!DataContainer(); // call finalizer m_isDisposed = true; } // Finalizer !DataContainer() { // free unmanaged data //DataProvider::DeleteUnmanagedData(m_unmanagedData); } private: bool m_isDisposed; // must be set to false };
The Root of all Evil 万恶之源
In C#, all classes are managed by the garbage collector. However, some things just can’t be expressed in pure managed code. In these cases you’ll need to store unmanaged data in a managed class(. Examples are file handles, sockets, or objects created by unmanaged functions/frameworks.
在C#中,所有类均由垃圾收集器管理。但是,有些事情无法用纯托管代码表达。在这些情况下,您需要将未管理的数据存储在托管类中。示例是由非托管函数/框架创建的文件句柄,套接字或对象。
So, here’s an example of a C# class containing unmanaged data:
class DataContainer { public DataContainer() { m_unmanagedData = DataProvider.CreateUnmanagedData(); } private IntPtr m_unmanagedData; }
The equivalent C++/CLI example would look like this:
ref class DataContainer { public: DataContainer() { m_unmanagedData = DataProvider::CreateUnmanagedData(); } private: IntPtr m_unmanagedData; };
The question now is: When gets the unmanaged data deleted? 何时删除非托管数据?
Finalizer 终结器
Since our class DataContainer
is a managed class, it is managed by .NET’s garbage collector. When the garbage collector determines that our instance of DataContainer
is no longer needed, the object’s finalizer is called. So, that’s are good point to delete our unmanaged data.
由于我们的类DataContainer
是托管类,因此它由.NET的垃圾收集器管理。当垃圾收集器确定我们的情况DataContainer
是不再需要,该对象的终结器被调用。因此,这是删除我们的非托管数据的好点。
Note: Finalizers are also called non-deterministic destructors because the programmer has no influence over when the garbage collector will call the finalizer (once the object went out of scope). For deterministic destructors (explained in the next section), on the other hand, the programmer has full control when they are called.
注:终结也被称为非确定性的析构函数,因为程序员有超过没有影响时,垃圾收集器会调用终结(一旦对象去的范围了)。对于确定性的析构函数(下一节介绍),而另一方面,程序员被调用时完全控制。
In C# the finalizer method (internally named Finalize()
) is created by using C++’s destructor notation (~DataContainer
):
在C#中,终结器方法(内部名为Finalize()
)是使用C ++的析构符号(~DataContainer
)创建的:
class DataContainer { public DataContainer() { m_unmanagedData = DataProvider.CreateUnmanagedData(); } ~DataContainer() { DataProvider.DeleteUnmanagedData(m_unmanagedData); } private IntPtr m_unmanagedData; }
In C++/CLI the notation ~DataContainer()
is already reserved for deterministic destructors (because all destructors are deterministic in C++). So, here we must use the notation !DataContainer
instead:
ref class DataContainer { public: DataContainer() { m_unmanagedData = DataProvider::CreateUnmanagedData(); } !DataContainer() { DataProvider.DeleteUnmanagedData(m_unmanagedData); } private: IntPtr m_unmanagedData; };
Note: Although you can declare a finalizer public in C++/CLI, it won’t be. So, don’t bother with its visibility. (In C# you get a compiler error when specifying anything but private visibility for the finalizer.)
IDisposable
Since finalizers are non-deterministic, you have no control over when they will be called. When working with unmanaged data it may, however, be desirable to control the point of time when this unmanaged data will be deleted. The easiest way to do this: create a method for that.
由于终结器是不确定的,因此您无法控制何时调用它们。但是,当使用非托管数据时,可能希望控制将删除该非托管数据的时间点。最简单的方法:为此创建一个方法。
The .NET framework provides a standard name for this method: Dispose()
, defined by the IDisposable
interface. This method is also called a “deterministic destructor” (whereas finalizers are non-deterministic destructors). Here’s the implementation in C#:
.NET框架为此方法提供了标准名称:Dispose()
,由IDisposable
接口定义。这种方法也被称为“ 确定性析构函数”(而终结是非确定性的析构函数)。这是C#中的实现:
class DataContainer : IDisposable { public DataContainer() { m_unmanagedData = DataProvider.CreateUnmanagedData(); } public void Dispose() { if (m_isDisposed) return; DataProvider.DeleteUnmanagedData(m_unmanagedData); m_isDisposed = true; } private bool m_sDisposed= false; private IntPtr m_unmanagedData; }
In C# you can either call the Dispose()
method directly or use a using
block. Note that we’ve added m_isDisposed
to prevent the programmer from calling Dispose()
multiple times.
在C#中,您可以Dispose()
直接调用该方法或使用一个using
块。请注意,我们已经添加m_isDisposed
了防止程序员Dispose()
多次调用的功能。
In C++/CLI the Dispose()
method is automatically created (and IDisposable
is implemented) when creating a destructor (~DataContainer
):
ref class DataContainer { public: DataContainer() : m_isDisposed(false) { m_unmanagedData = DataProvider::CreateUnmanagedData(); } ~DataContainer() { if (m_isDisposed) return; DataProvider::DeleteUnmanagedData(m_unmanagedData); m_isDisposed = true; } private: bool m_isDisposed; IntPtr m_unmanagedData; };
In C++/CLI a deterministic destructor (or the Dispose()
method) is called automatically when:
- a class instance is on the stack (instead of the managed heap) and goes out of scope
-
a class instance is on the managed heap and gets deleted via
delete myVar;
This is identical with how you would “call” destructors in C++.
在C ++ / CLI中,Dispose()
在以下情况下会自动调用确定性析构函数(或方法):
- 一个类实例在堆栈上(而不是托管堆上)并且超出范围
-
类实例在托管堆上,并通过删除
delete myVar;
这与您在C ++中“调用”析构函数的方式相同。
Combining Dispose and Finalizer 结合 Dispose 和终结
Since the programmer can forget to call Dispose()
, it’s important to free unmanaged data in the finalizer (as well) to avoid memory leaks. So, often you want to combine Dispose()
and the finalizer. Here’s how.
In C#, simply call Dispose()
from the finalizer. Note that the finalizer will be called in any case, so the unmanaged data is freed even if the programmer forgets to call Dispose()
.
由于程序员可以忘记调用Dispose()
,因此在终结器中释放非托管数据也很重要,以避免内存泄漏。因此,通常您想将其Dispose()
与终结器结合在一起。就是这样。
在C#中,只需Dispose()
从终结器调用即可。请注意,在任何情况下都将调用终结器,因此即使程序员忘记调用,也将释放非托管数据Dispose()
。
class DataContainer : IDisposable { public DataContainer() { m_unmanagedData = DataProvider.CreateUnmanagedData(); } public void Dispose() { if (m_isDisposed) return; DataProvider.DeleteUnmanagedData(m_unmanagedData); m_isDisposed = true; } // Finalizer ~DataContainer() { Dispose(); } private bool m_disposed = false; private IntPtr m_unmanagedData; }
In C++/CLI we do the opposite: we call the finalizer (which isn’t possible in C#). This way is the cleaner. I’ll explain this a little later on.
ref class DataContainer { public: DataContainer() : m_isDisposed(false) { m_unmanagedData = DataProvider::CreateUnmanagedData(); } ~DataContainer() { if (m_isDisposed) return; this->!DataContainer(); m_isDisposed = true; } // Finalizer !DataContainer() { DataProvider::DeleteUnmanagedData(m_unmanagedData); } private: bool m_isDisposed; IntPtr m_unmanagedData; };
Managed Data 托管数据
Beside unmanaged data, a managed class can also contain managed data, i.e. instances of managed classes implementing IDisposable
. Managed data is different from unmanaged data in that it should be disposed in Dispose()
but not in the finalizer. This is because instances of managed classes may already have been garbage collected when the finalizer runs.
To avoid code duplication, Dispose()
and the finalizer should be implemented like this (in pseudo-code):
除了非托管数据,托管类也可以包含管理数据,即实现管理类的实例IDisposable
。托管数据与非托管数据的不同之处在于,应将其放置在终结器中,Dispose()
而不要放置在终结器中。这是因为在运行终结器时,可能已经对垃圾桶的实例进行了垃圾回收。
为避免代码重复,Dispose()
终结器应以以下方式实现(在伪代码中):
void Dispose() { DisposeAllManagedData(); Finalizer(); } void Finalizer() { FreeAllUnmanagedData(); }
This is why I called the C++/CLI way the “cleaner” way above. It implements the whole thing just like our pseudo-code:
这就是为什么我将C ++ / CLI方式称为上面的“更清洁”方式的原因。它实现了整个过程,就像我们的伪代码一样:
ref class DataContainer { public: ... ~DataContainer() { if (m_isDisposed) return; delete m_managedData; // dispose managed data this->!DataContainer(); // call finalizer m_isDisposed = true; } // Finalizer !DataContainer() { DataProvider::DeleteUnmanagedData(m_unmanagedData); // free unmanaged data } private: bool m_isDisposed; IntPtr m_unmanagedData; IDisposable^ m_managedData; };
In C#, on the other hand, we can’t call the finalizer. So, we need to add a helper method called Dispose(bool)
:
另一方面,在C#中,我们不能调用终结器。因此,我们需要添加一个名为的辅助方法
class DataContainer : IDisposable { ... public void Dispose() { Dipose(true); } ~DataContainer() { Dispose(false); } protected virtual void Dispose(bool disposing) { if (m_isDisposed) return; if (disposing) { m_managedData.Dispose(); } DataProvider.DeleteUnmanagedData(m_unmanagedData); m_isDisposed = true; } private bool m_disposed = false; private IntPtr m_unmanagedData; private IDisposable m_managedData; }
Note: The method Dispose(bool)
is virtual
. The idea behind this is that child classes can override this method to perform their own disposing. See below for more information. Note also that the C++/CLI compiler automatically creates this method when a class has a destructor. You can’t, however, use this method directly. It’s only visible from C# (or Visual Basic).
注意:方法Dispose(bool)
是virtual
。其背后的想法是子类可以重写此方法以执行自己的处置。请参阅下面的详细信息。还要注意,当类具有析构函数时,C ++ / CLI编译器会自动创建此方法。但是,您不能直接使用此方法。仅在C#(或Visual Basic)中可见。
SuppressFinalize
The default dispose implementation pattern (as shown in IDisposable’s help page) also adds the line GC.SuppressFinalize(this);
to the Dispose()
method. What does this method do and why do we need it?
GC.SuppressFinalize()
simply prevents the finalizer from being called. Since the finalizer’s only task is to free unmanaged data, it doesn’t need to be called if Dispose()
was already called (and already freed all unmanaged data by calling the finalizer). Using GC.SuppressFinalize()
give a small performance improvement but nothing more.
In C# the Dispose()
method changes like this:
默认的dispose实现模式(如IDisposable的帮助页面所示)还将行添加GC.SuppressFinalize(this);
到Dispose()
方法中。这种方法有什么作用,为什么我们需要它?
GC.SuppressFinalize()
只是防止终结器被调用。由于终结器的唯一任务是释放非托管数据,因此如果Dispose()
已被调用(并且已经通过调用终结器来释放所有非托管数据)就不需要调用它。使用GC.SuppressFinalize()
可以改善性能,但仅此而已。
public void Dispose() { Dipose(true); GC.SuppressFinalize(this); }
In C++/CLI the destructor doesn’t change at all. That’s because the C++/CLI compiler automatically adds this code line to the destructor. (You can read about this and see a decompiled destructor here. Search for “SuppressFinalize”.)
~DataContainer() { if (m_isDisposed) return; delete m_managedData; // dispose managed data this->!DataContainer(); // call finalizer m_isDisposed = true; // GC.SuppressFinalize(this) is automatically inserted here }
Inheritance 继承
The default dispose implementation pattern used in the previous sections create a method called Dispose(bool)
. This method is protected virtual
and is meant to be overridden by child classes – in case they need to dispose some data of their own.
In C#, an implementation must
- first check whether it already has been disposed,
- then dispose everything,
-
and then call the base method.
The base method is called last to ensure that child classes are disposed before their parent classes. This is how destructors work in C++ and Dispose()
mimics this behavior.
前面几节中使用的默认处理实现模式会创建一个名为的方法Dispose(bool)
。该 方法是 protected virtual的,
子类将覆盖此方法,并且该方法将被子类覆盖,以防他们需要处置自己的一些数据。
在C#中,实现必须
- 首先检查它是否已经被处置,
- 然后处理一切
-
然后调用基类方法。
base方法最后调用,以确保将子类放置在其父类之前。这就是析构函数在C ++中的工作方式,并且Dispose()
模仿了这种行为。
protected virtual void Dispose(bool disposing) { if (m_isDisposed) return; if (disposing) { m_managedData.Dispose(); } DataProvider.DeleteUnmanagedData(m_unmanagedData); m_isDisposed = true; // Dispose parent classes after child classes base.Dispose(disposing); }
Note: Each child class must manage its own m_isDisposed
field.
注意:每个子类都必须管理自己的 m_isDisposed
字段。
In C++/CLI, again, the destructor remains the same. This is because it mimics the C++ destructor’s behavior which automatically calls its parent destructor.
同样,在C ++ / CLI中,析构函数保持不变。这是因为它模仿了C ++析构函数的行为,该行为会自动调用其父析构函数。
~DataContainer() {
if (m_isDisposed)
return;
delete m_managedData; // dispose managed data
this->!DataContainer(); // call finalizer
m_isDisposed = true;
// GC.SuppressFinalize(this) is automatically inserted here
// Base destructor is automatically called here
}
When to Use IDisposable: 3 easy rules 何时使用IDisposable:3条简单规则
At this point I’d like to cite three easy rules when to use IDisposable
. These rules were created by Stephen Cleary and I take no credit for them. I do, however, disagree with some statements (such as “No classes should be responsible for multiple unmanaged resources.”) and have adopted the rules in this case (see the link for the original description).
在这一点上,我想列举三个简单的规则IDisposable
。这些规则是由史蒂芬·克雷里(Stephen Cleary)创立的,我对此一无所知。但是,我确实不同意某些声明(例如“没有类应该负责多个非托管资源。”),并且在这种情况下采用了规则(请参见原始说明的链接)。
Rule 1: Don’t do it (unless you need to). 规则1:请勿这样做(除非您需要这样做)。
There are only two situations when IDisposable
does need to be implemented:
- The class owns unmanaged resources.
-
The class owns managed (
IDisposable
) resources.
只有两种情况IDisposable
需要实施:
- 该类拥有非托管资源。
-
该类拥有托管(
IDisposable
)资源。
Rule 2: For a class owning managed resources, implement IDisposable (but not a finalizer) 规则2:对于一类拥有管理资源,实现IDisposable(但不是终结)
This implementation of IDisposable
should only call Dispose()
for each owned resource. It should not set anything to null
. 此实现IDisposable
仅应调用Dispose()
每个拥有的资源。不应将任何内容设置为null
。
The class should not have a finalizer. 该类不应具有终结器。
Rule 3: For a class owning at least one unmanaged resource, implement both IDisposable and a finalizer 规则3:对于一类拥有至少一个非托管资源,同时实现了IDisposable和终结
The finalizer should free the unmanaged resource, Dispose
should dispose any managed resource and then call the finalizer.
终结器应释放非托管资源,Dispose
应处置任何托管资源,然后调用终结器。