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

示例

下面的示例验证Finalize对象的重写时,调用方法Finalize被销毁。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

Object类提供的实现不Finalize方法,并在垃圾回收器将派生自的类型未标记Object终止除非它们重写Finalize方法。The 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.ReRegisterForFinalizeGC.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.

重要

如果SafeHandle对象可包装非托管的资源,建议的替代项是实现使用安全句柄的释放模式并不重写FinalizeIf 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派生类型中必须调用其基类型实现FinalizeEvery implementation of Finalize in a derived type must call its base type's implementation of Finalize. 这是其中的应用程序代码可以调用的唯一情况FinalizeThis 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.

VisualC++还提供用于实现其自己的语法Finalize方法。Visual C++ also provides its own syntax for implementing the Finalize method. 有关详细信息,请参阅的"析构函数和终结器"部分如何:定义和使用类和结构 (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. 释放资源立即,您还可以选择实现释放模式IDisposable接口。To release resources immediately, you can also choose to implement the dispose pattern and the IDisposable interface. IDisposable.Dispose实现可由使用者类的释放非托管的资源,并且你可以使用Finalize方法来释放非托管的资源的事件中Dispose不会调用方法。The 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 后它已清除垃圾回收期间,可以执行几乎任何操作,包括复活 (亦即,使该对象可访问再次) 的对象。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

创建可靠的终结器通常是很困难,因为不能应用程序的状态做出假设,因为未经处理的系统异常,如OutOfMemoryExceptionStackOverflowException终止终结器。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.SafeHandle:The .NET Framework provides the following classes in the Microsoft.Win32 namespace that are derived from System.Runtime.InteropServices.SafeHandle:

下面的示例使用释放模式使用安全句柄而不是替代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. 作为返回两个注册表句柄out参数的 Windows RegOpenKeyEx函数调用传递给SafeRegistryHandle构造函数。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
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

适用于

另请参阅