首页 > 编程语言 > 详细

IDisposable, Finalizer, and SuppressFinalize in C# and C++/CLI

时间:2019-12-30 12:27:55      阅读:75      评论:0      收藏:0      [点我收藏+]

转载至  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.

 

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:

 
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

  1. first check whether it already has been disposed,
  2. then dispose everything,
  3. 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.

 
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.

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.

  ~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 

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).

Rule 1: Don’t do it (unless you need to).

There are only two situations when IDisposable does need to be implemented:

  • The class owns unmanaged resources.
  • The class owns managed (IDisposable) resources.

Rule 2: For a class owning managed resources, implement IDisposable (but not a finalizer)

This implementation of IDisposable should only call Dispose() for each owned resource. It should not set anything to 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

The finalizer should free the unmanaged resource, Dispose should dispose any managed resource and then call the finalizer.

IDisposable, Finalizer, and SuppressFinalize in C# and C++/CLI

原文:https://www.cnblogs.com/jshchg/p/12118942.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!