Object.Finalize Object.Finalize Object.Finalize Object.Finalize Method

定义

在垃圾回收将某一对象回收前允许该对象尝试释放资源并执行其他清理操作。Allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.

!Object ()
~Object ();
abstract member Finalize : unit -> unit
override this.Finalize : unit -> unit
Finalize ()

示例

下面的示例验证当重FinalizeFinalize的对象被销毁时调用的方法。The following example verifies that the Finalize method is called when an object that overrides Finalize is destroyed. 请注意,在生产应用程序中, Finalize将重写方法以释放由该对象占用的非托管资源。Note that, in a production application, the Finalize method would be overridden to release unmanaged resources held by the object. 另请注意, C#该示例提供析构函数,而不Finalize是重写方法。Also note that the C# example provides a destructor instead of overriding the Finalize method.

using System;
using System.Diagnostics;

public class ExampleClass
{
   Stopwatch sw;
   
   public ExampleClass()
   {
      sw = Stopwatch.StartNew();
      Console.WriteLine("Instantiated object");
   } 

   public void ShowDuration()
   {
      Console.WriteLine("This instance of {0} has been in existence for {1}",
                        this, sw.Elapsed);
   }
   
   ~ExampleClass()
   {
      Console.WriteLine("Finalizing object");
      sw.Stop();
      Console.WriteLine("This instance of {0} has been in existence for {1}",
                        this, sw.Elapsed);
   }
}

public class Demo
{
   public static void Main()
   {
      ExampleClass ex = new ExampleClass();
      ex.ShowDuration();
   }
}
// The example displays output like the following:
//    Instantiated object
//    This instance of ExampleClass has been in existence for 00:00:00.0011060
//    Finalizing object
//    This instance of ExampleClass has been in existence for 00:00:00.0036294
Imports System.Diagnostics

Public Class ExampleClass
   Dim sw As StopWatch
   
   Public Sub New()
      sw = Stopwatch.StartNew()
      Console.WriteLine("Instantiated object")
   End Sub 

   Public Sub ShowDuration()
      Console.WriteLine("This instance of {0} has been in existence for {1}",
                        Me, sw.Elapsed)
   End Sub
   
   Protected Overrides Sub Finalize()
      Console.WriteLine("Finalizing object")
      sw.Stop()
      Console.WriteLine("This instance of {0} has been in existence for {1}",
                        Me, sw.Elapsed)
   End Sub
End Class

Module Demo
   Public Sub Main()
      Dim ex As New ExampleClass()
      ex.ShowDuration()
   End Sub
End Module
' The example displays output like the following:
'    Instantiated object
'    This instance of ExampleClass has been in existence for 00:00:00.0011060
'    Finalizing object
'    This instance of ExampleClass has been in existence for 00:00:00.0036294

有关替代Finalize方法的其他示例, GC.SuppressFinalize请参见方法。For an additional example that overrides the Finalize method, see the GC.SuppressFinalize method.

注解

Finalize方法用于在销毁对象之前对当前对象占用的非托管资源执行清理操作。The Finalize method is used to perform cleanup operations on unmanaged resources held by the current object before the object is destroyed. 方法是受保护的,因此只能通过此类或派生类访问。The method is protected and therefore is accessible only through this class or through a derived class.

本节内容:In this section:

终止的工作方式How finalization works

类不提供Finalize方法的实现,垃圾回收器不会将派生自Object的类型标记为终止,除非它们重写Finalize方法。 ObjectThe Object class provides no implementation for the Finalize method, and the garbage collector does not mark types derived from Object for finalization unless they override the Finalize method.

如果类型确实重写Finalize方法,则垃圾回收器会将类型的每个实例的条目添加到称为终止队列的内部结构。If a type does override the Finalize method, the garbage collector adds an entry for each instance of the type to an internal structure called the finalization queue. 终止队列包含托管堆中的所有对象的条目,在垃圾回收器可以回收内存之前,必须先运行终止代码。The finalization queue contains entries for all the objects in the managed heap whose finalization code must run before the garbage collector can reclaim their memory. 然后,垃圾回收器会Finalize在以下条件下自动调用方法:The garbage collector then calls the Finalize method automatically under the following conditions:

  • 在垃圾回收器发现对象不可访问后,除非已通过调用GC.SuppressFinalize方法免除了该对象的终止。After the garbage collector has discovered that an object is inaccessible, unless the object has been exempted from finalization by a call to the GC.SuppressFinalize method.

  • 仅在 .NET Framework的情况下,在应用程序域的关闭过程中,除非该对象免于终止。On .NET Framework only, during shutdown of an application domain, unless the object is exempt from finalization. 在关闭期间,即使仍可访问的对象也会完成。During shutdown, even objects that are still accessible are finalized.

Finalize仅在给定的实例上自动调用一次,除非该对象是使用之类GC.ReRegisterForFinalize的机制重新注册的, GC.SuppressFinalize并且随后未调用方法。Finalize is automatically called only once on a given instance, unless the object is re-registered by using a mechanism such as GC.ReRegisterForFinalize and the GC.SuppressFinalize method has not been subsequently called.

Finalize操作具有以下限制:Finalize operations have the following limitations:

  • 不确定终结器执行时的准确时间。The exact time when the finalizer executes is undefined. 若要确保类的实例的资源的确定性版本,请实现Close方法或IDisposable.Dispose提供实现。To ensure deterministic release of resources for instances of your class, implement a Close method or provide a IDisposable.Dispose implementation.

  • 不能保证两个对象的终结器以任何特定顺序运行,即使一个对象引用另一个对象也是如此。The finalizers of two objects are not guaranteed to run in any specific order, even if one object refers to the other. 也就是说,如果对象 A 具有对对象 B 的引用,并且都具有终结器, 在对象 A 的终结器启动时,对象 B 可能已被终结。That is, if Object A has a reference to Object B and both have finalizers, Object B might have already been finalized when the finalizer of Object A starts.

  • 不指定终结器在其上运行的线程。The thread on which the finalizer runs is unspecified.

Finalize以下异常情况下,该方法可能无法运行到完成或根本不会运行:The Finalize method might not run to completion or might not run at all under the following exceptional circumstances:

  • 如果另一个终结器无限期阻止(进入无限循环,则会尝试获取它永远无法获取的锁定,等等)。If another finalizer blocks indefinitely (goes into an infinite loop, tries to obtain a lock it can never obtain, and so on). 因为运行时尝试运行终结器来完成,所以如果终结器无限期阻塞,则可能不会调用其他终结器。Because the runtime tries to run finalizers to completion, other finalizers might not be called if a finalizer blocks indefinitely.

  • 如果进程终止,无需给运行时提供清理的机会。If the process terminates without giving the runtime a chance to clean up. 在这种情况下,运行时的进程终止通知是 DLL_PROCESS_DETACH 通知。In this case, the runtime's first notification of process termination is a DLL_PROCESS_DETACH notification.

仅在关闭过程中,运行时继续完成对象,但可继续减少可终结对象的数量。The runtime continues to finalize objects during shutdown only while the number of finalizable objects continues to decrease.

如果Finalize或的Finalize替代引发了异常,并且运行时不是由覆盖默认策略的应用程序承载,则运行时将终止进程,而不会try终止活动/ finally块或执行终结器。If Finalize or an override of Finalize throws an exception, and the runtime is not hosted by an application that overrides the default policy, the runtime terminates the process and no active try/finally blocks or finalizers are executed. 如果终结器无法释放或销毁资源,则此行为可确保进程完整性。This behavior ensures process integrity if the finalizer cannot free or destroy resources.

重写 Finalize 方法Overriding the Finalize method

应为使用Finalize非托管资源的类(例如,在垃圾回收过程中丢弃使用非托管资源的文件句柄或数据库连接)进行重写。You should override Finalize for a class that uses unmanaged resources, such as file handles or database connections that must be released when the managed object that uses them is discarded during garbage collection. 不应实现Finalize托管对象的方法,因为垃圾回收器会自动释放托管资源。You shouldn't implement a Finalize method for managed objects because the garbage collector releases managed resources automatically.

重要

如果对象可用于包装非托管资源,则建议的替代方法是使用安全句柄实现 dispose 模式,而不是重Finalize写。 SafeHandleIf a SafeHandle object is available that wraps your unmanaged resource, the recommended alternative is to implement the dispose pattern with a safe handle and not override Finalize. 有关详细信息,请参阅SafeHandle 替代部分。For more information, see The SafeHandle alternative section.

默认Object.Finalize情况下,此方法不执行任何操作, Finalize但你应仅在必要时重写,并且仅释放非托管资源。The Object.Finalize method does nothing by default, but you should override Finalize only if necessary, and only to release unmanaged resources. 如果某个终止操作运行,则回收内存要花费更长时间,因为它需要至少两个垃圾回收。Reclaiming memory tends to take much longer if a finalization operation runs, because it requires at least two garbage collections. 此外,还应为仅引用Finalize类型重写方法。In addition, you should override the Finalize method for reference types only. 公共语言运行时仅终结引用类型。The common language runtime only finalizes reference types. 它忽略值类型上的终结器。It ignores finalizers on value types.

Object.Finalize方法的范围为protectedThe scope of the Object.Finalize method is protected. 重写类中的方法时,应保持此有限范围。You should maintain this limited scope when you override the method in your class. 通过使Finalize方法受到保护,可以防止应用程序的用户直接调用对象的Finalize方法。By keeping a Finalize method protected, you prevent users of your application from calling an object's Finalize method directly.

派生类型中Finalize的每个实现都必须调用其基类型的Finalize实现。Every implementation of Finalize in a derived type must call its base type's implementation of Finalize. 这是允许应用程序代码调用Finalize的唯一情况。This is the only case in which application code is allowed to call Finalize. 对象的Finalize方法不应在其基类以外的任何对象上调用方法。An object's Finalize method shouldn't call a method on any objects other than that of its base class. 这是因为调用的其他对象可以与调用对象同时收集,如公共语言运行时关闭的情况。This is because the other objects being called could be collected at the same time as the calling object, such as in the case of a common language runtime shutdown.

备注

C#编译器不允许重写Finalize方法。The C# compiler does not allow you to override the Finalize method. 而是通过实现类的析构函数来提供终结器。Instead, you provide a finalizer by implementing a destructor for your class. C#析构函数自动调用其基类的析构函数。A C# destructor automatically calls the destructor of its base class.

视觉C++对象还提供其自己的语法来Finalize实现方法。Visual C++ also provides its own syntax for implementing the Finalize method. 有关详细信息,请参阅how to:定义和使用类和结构(C++/cli)For more information, see the "Destructors and finalizers" section of How to: Define and Consume Classes and Structs (C++/CLI).

由于垃圾回收是不确定的,因此,在垃圾回收器执行终止时,您不会精确地知道。Because garbage collection is non-deterministic, you do not know precisely when the garbage collector performs finalization. 若要立即释放资源,还可以选择实现dispose 模式IDisposable接口。To release resources immediately, you can also choose to implement the dispose pattern and the IDisposable interface. 实现可由类的使用者调用以释放非托管资源,并且可以Dispose在未调用方法Finalize的情况下使用方法来释放非托管资源。 IDisposable.DisposeThe IDisposable.Dispose implementation can be called by consumers of your class to free unmanaged resources, and you can use the Finalize method to free unmanaged resources in the event that the Dispose method is not called.

Finalize几乎可以执行任何操作,包括 resurrecting 一个对象(即,在垃圾回收过程中清除对象后使对象再次可访问)。Finalize can take almost any action, including resurrecting an object (that is, making the object accessible again) after it has been cleaned up during garbage collection. 但是,对象只能复活一次;Finalize在垃圾回收期间,不能对复活对象调用。However, the object can only be resurrected once; Finalize cannot be called on resurrected objects during garbage collection.

SafeHandle 备用方法The SafeHandle alternative

创建可靠的终结器通常很难,因为你无法对应用程序的状态进行假设,并且因为未处理的系统OutOfMemoryException异常StackOverflowException (如和)终止终结器。Creating reliable finalizers is often difficult, because you cannot make assumptions about the state of your application, and because unhandled system exceptions such as OutOfMemoryException and StackOverflowException terminate the finalizer. 您可以使用从System.Runtime.InteropServices.SafeHandle类派生的对象来包装非托管资源,并在不使用终结器的情况下实现 dispose 模式,而不是实现类的终结器以释放非托管资源。Instead of implementing a finalizer for your class to release unmanaged resources, you can use an object that is derived from the System.Runtime.InteropServices.SafeHandle class to wrap your unmanaged resources, and then implement the dispose pattern without a finalizer. .NET Framework 提供Microsoft.Win32命名空间中的以下类,这些类派生自System.Runtime.InteropServices.SafeHandleThe .NET Framework provides the following classes in the Microsoft.Win32 namespace that are derived from System.Runtime.InteropServices.SafeHandle:

下面的示例将dispose 模式与安全句柄一起使用,而Finalize不是重写方法。The following example uses the dispose pattern with safe handles instead of overriding the Finalize method. 它定义一个FileAssociation类,该类包装有关处理具有特定文件扩展名的文件的应用程序的注册表信息。It defines a FileAssociation class that wraps registry information about the application that handles files with a particular file extension. Windows RegOpenKeyEx函数调用SafeRegistryHandle将两out个作为参数返回的注册表句柄传递到构造函数。The two registry handles returned as out parameters by Windows RegOpenKeyEx function calls are passed to the SafeRegistryHandle constructor. 然后,该类型Dispose的受保护方法SafeRegistryHandle.Dispose会调用方法来释放这两个句柄。The type's protected Dispose method then calls the SafeRegistryHandle.Dispose method to free these two handles.

using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;

public class FileAssociationInfo : IDisposable
{
   // Private variables.
   private String ext;
   private String openCmd;
   private String args;
   private SafeRegistryHandle hExtHandle, hAppIdHandle;

   // Windows API calls.
   [DllImport("advapi32.dll", CharSet= CharSet.Auto, SetLastError=true)]
   private static extern int RegOpenKeyEx(IntPtr hKey, 
                  String lpSubKey, int ulOptions, int samDesired,
                  out IntPtr phkResult);
   [DllImport("advapi32.dll", CharSet= CharSet.Unicode, EntryPoint = "RegQueryValueExW",
              SetLastError=true)]
   private static extern int RegQueryValueEx(IntPtr hKey,
                  string lpValueName, int lpReserved, out uint lpType, 
                  string lpData, ref uint lpcbData);   
   [DllImport("advapi32.dll", SetLastError = true)]
   private static extern int RegSetValueEx(IntPtr hKey, [MarshalAs(UnmanagedType.LPStr)] string lpValueName,
                  int Reserved, uint dwType, [MarshalAs(UnmanagedType.LPStr)] string lpData,
                  int cpData);
   [DllImport("advapi32.dll", SetLastError=true)]
   private static extern int RegCloseKey(UIntPtr hKey);

   // Windows API constants.
   private const int HKEY_CLASSES_ROOT = unchecked((int) 0x80000000);
   private const int ERROR_SUCCESS = 0;

    private const int KEY_QUERY_VALUE = 1;
    private const int KEY_SET_VALUE = 0x2;
   
   private const uint REG_SZ = 1;
   
   private const int MAX_PATH = 260;
   
   public FileAssociationInfo(String fileExtension)
   {
      int retVal = 0;
      uint lpType = 0;
                  
      if (!fileExtension.StartsWith("."))
             fileExtension = "." + fileExtension;
      ext = fileExtension;
       
      IntPtr hExtension = IntPtr.Zero;
      // Get the file extension value.
      retVal = RegOpenKeyEx(new IntPtr(HKEY_CLASSES_ROOT), fileExtension, 0, KEY_QUERY_VALUE, out hExtension);
      if (retVal != ERROR_SUCCESS) 
         throw new Win32Exception(retVal);
      // Instantiate the first SafeRegistryHandle.
      hExtHandle = new SafeRegistryHandle(hExtension, true);
      
      string appId = new string(' ', MAX_PATH);
      uint appIdLength = (uint) appId.Length;
      retVal = RegQueryValueEx(hExtHandle.DangerousGetHandle(), String.Empty, 0, out lpType, appId, ref appIdLength);
      if (retVal != ERROR_SUCCESS)
         throw new Win32Exception(retVal);
      // We no longer need the hExtension handle.
      hExtHandle.Dispose();

      // Determine the number of characters without the terminating null.
      appId = appId.Substring(0, (int) appIdLength / 2 - 1) + @"\shell\open\Command";

      // Open the application identifier key.
      string exeName = new string(' ', MAX_PATH);
      uint exeNameLength = (uint) exeName.Length;
      IntPtr hAppId;
      retVal = RegOpenKeyEx(new IntPtr(HKEY_CLASSES_ROOT), appId, 0, KEY_QUERY_VALUE | KEY_SET_VALUE,
                            out hAppId);
       if (retVal != ERROR_SUCCESS) 
         throw new Win32Exception(retVal);

      // Instantiate the second SafeRegistryHandle.
      hAppIdHandle = new SafeRegistryHandle(hAppId, true);

      // Get the executable name for this file type.
      string exePath = new string(' ', MAX_PATH);
      uint exePathLength = (uint) exePath.Length;
      retVal = RegQueryValueEx(hAppIdHandle.DangerousGetHandle(), String.Empty, 0, out lpType, exePath, ref exePathLength);
      if (retVal != ERROR_SUCCESS)
         throw new Win32Exception(retVal);
           
      // Determine the number of characters without the terminating null.
      exePath = exePath.Substring(0, (int) exePathLength / 2 - 1);
      // Remove any environment strings.
      exePath = Environment.ExpandEnvironmentVariables(exePath);

      int position = exePath.IndexOf('%');
      if (position >= 0) {
         args = exePath.Substring(position);
         // Remove command line parameters ('%0', etc.).
         exePath = exePath.Substring(0, position).Trim();
      }
      openCmd = exePath;   
   }

   public String Extension
   { get { return ext; } }
   
   public String Open
   { get { return openCmd; } 
     set {
        if (hAppIdHandle.IsInvalid | hAppIdHandle.IsClosed)
           throw new InvalidOperationException("Cannot write to registry key."); 
        if (! File.Exists(value)) {
           string message = String.Format("'{0}' does not exist", value);
           throw new FileNotFoundException(message); 
        }
        string cmd = value + " %1";
        int retVal = RegSetValueEx(hAppIdHandle.DangerousGetHandle(), String.Empty, 0, 
                                   REG_SZ, value, value.Length + 1);
        if (retVal != ERROR_SUCCESS)
           throw new Win32Exception(retVal);                          
     } }
   
   public void Dispose() 
   {
      Dispose(true);
      GC.SuppressFinalize(this);
   }   
   
   protected void Dispose(bool disposing)
   {
      // Ordinarily, we release unmanaged resources here; 
      // but all are wrapped by safe handles.
      
      // Release disposable objects.
      if (disposing) {
         if (hExtHandle != null) hExtHandle.Dispose();
         if (hAppIdHandle != null) hAppIdHandle.Dispose();
      }
   }
}
Imports Microsoft.Win32.SafeHandles
Imports System.ComponentModel
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Text

Public Class FileAssociationInfo : Implements IDisposable
   ' Private variables.
   Private ext As String
   Private openCmd As String
   Private args As String
   Private hExtHandle, hAppIdHandle As SafeRegistryHandle

   ' Windows API calls.
   Private Declare Unicode Function RegOpenKeyEx Lib"advapi32.dll" _
                   Alias "RegOpenKeyExW" (hKey As IntPtr, lpSubKey As String, _
                   ulOptions As Integer, samDesired As Integer, _
                   ByRef phkResult As IntPtr) As Integer
   Private Declare Unicode Function RegQueryValueEx Lib "advapi32.dll" _
                   Alias "RegQueryValueExW" (hKey As IntPtr, _
                   lpValueName As String, lpReserved As Integer, _
                   ByRef lpType As UInteger, lpData As String, _
                   ByRef lpcbData As UInteger) As Integer   
   Private Declare Function RegSetValueEx Lib "advapi32.dll" _
                  (hKey As IntPtr, _
                  <MarshalAs(UnmanagedType.LPStr)> lpValueName As String, _
                  reserved As Integer, dwType As UInteger, _
                  <MarshalAs(UnmanagedType.LPStr)> lpData As String, _
                  cpData As Integer) As Integer 
   Private Declare Function RegCloseKey Lib "advapi32.dll" _
                  (hKey As IntPtr) As Integer

   ' Windows API constants.
   Private Const HKEY_CLASSES_ROOT As Integer = &h80000000
   Private Const ERROR_SUCCESS As Integer = 0

   Private Const KEY_QUERY_VALUE As Integer = 1
   Private Const KEY_SET_VALUE As Integer = &h2
   
   Private REG_SZ As UInteger = 1
   
   Private Const MAX_PATH As Integer  = 260
   
   Public Sub New(fileExtension As String)
      Dim retVal As Integer = 0
      Dim lpType As UInteger = 0
                  
      If Not fileExtension.StartsWith(".") Then 
         fileExtension = "." + fileExtension
      End If   
      ext = fileExtension
       
      Dim hExtension As IntPtr = IntPtr.Zero
      ' Get the file extension value.
      retVal = RegOpenKeyEx(New IntPtr(HKEY_CLASSES_ROOT), fileExtension, 0, 
                            KEY_QUERY_VALUE, hExtension)
      if retVal <> ERROR_SUCCESS Then 
         Throw New Win32Exception(retVal)
      End If  
      ' Instantiate the first SafeRegistryHandle.
      hExtHandle = New SafeRegistryHandle(hExtension, True)
      
      Dim appId As New String(" "c, MAX_PATH)
      Dim appIdLength As UInteger = CUInt(appId.Length)
      retVal = RegQueryValueEx(hExtHandle.DangerousGetHandle(), String.Empty, _
                               0, lpType, appId, appIdLength)
      if retVal <> ERROR_SUCCESS Then
         Throw New Win32Exception(retVal)
      End If   
      ' We no longer need the hExtension handle.
      hExtHandle.Dispose()

      ' Determine the number of characters without the terminating null.
      appId = appId.Substring(0, CInt(appIdLength) \ 2 - 1) + "\shell\open\Command"

      ' Open the application identifier key.
      Dim exeName As New string(" "c, MAX_PATH)
      Dim exeNameLength As UInteger = CUInt(exeName.Length)
      Dim hAppId As IntPtr
      retVal = RegOpenKeyEx(New IntPtr(HKEY_CLASSES_ROOT), appId, 0, 
                            KEY_QUERY_VALUE Or KEY_SET_VALUE, hAppId)
      If retVal <> ERROR_SUCCESS Then 
         Throw New Win32Exception(retVal)
      End If   

      ' Instantiate the second SafeRegistryHandle.
      hAppIdHandle = New SafeRegistryHandle(hAppId, True)

      ' Get the executable name for this file type.
      Dim exePath As New string(" "c, MAX_PATH)
      Dim exePathLength As UInteger = CUInt(exePath.Length)
      retVal = RegQueryValueEx(hAppIdHandle.DangerousGetHandle(), _
                               String.Empty, 0, lpType, exePath, exePathLength)
      If retVal <> ERROR_SUCCESS Then
         Throw New Win32Exception(retVal)
      End If     
      ' Determine the number of characters without the terminating null.
      exePath = exePath.Substring(0, CInt(exePathLength) \ 2 - 1)
  
      exePath = Environment.ExpandEnvironmentVariables(exePath)
      Dim position As Integer = exePath.IndexOf("%"c)
      If position >= 0 Then
         args = exePath.Substring(position)
         ' Remove command line parameters ('%0', etc.).
         exePath = exePath.Substring(0, position).Trim()
      End If   
      openCmd = exePath
   End Sub

   Public ReadOnly Property Extension As String
      Get
         Return ext
      End Get
   End Property
   
   Public Property Open As String
      Get
         Return openCmd
      End Get    
      Set 
        If hAppIdHandle.IsInvalid Or hAppIdHandle.IsClosed Then
           Throw New InvalidOperationException("Cannot write to registry key.")
        End If    
        If Not File.Exists(value) Then
           Dim message As String = String.Format("'{0}' does not exist", value)
           Throw New FileNotFoundException(message) 
        End If
        Dim cmd As String = value + " %1"
        Dim retVal As Integer = RegSetValueEx(hAppIdHandle.DangerousGetHandle(), String.Empty, 0, 
                                              REG_SZ, value, value.Length + 1)
        If retVal <> ERROR_SUCCESS Then 
           Throw New Win32Exception(retVal)
        End If                             
      End Set
   End Property   
   
   Public Sub Dispose() _
      Implements IDisposable.Dispose 
      Dispose(true)
      GC.SuppressFinalize(Me)
   End Sub   
   
   Protected Sub Dispose(disposing As Boolean)
      ' Ordinarily, we release unmanaged resources here 
      ' but all are wrapped by safe handles.
      
      ' Release disposable objects.
      If disposing Then
         If hExtHandle IsNot Nothing Then hExtHandle.Dispose()
         If hAppIdHandle IsNot Nothing Then hAppIdHandle.Dispose()
      End If
   End Sub
End Class

适用于

另请参阅