System.Object.Finalize メソッド

この記事では、この API のリファレンス ドキュメントへの補足的な解説を提供します。

このFinalizeメソッドは、オブジェクトが破棄される前に、現在のオブジェクトによって保持されているアンマネージ リソースに対してクリーンup 操作を実行するために使用されます。 メソッドは保護されているため、このクラスまたは派生クラスを介してのみアクセスできます。

最終処理のしくみ

クラスは Object メソッドの実装を Finalize 提供しません。ガベージ コレクターは、メソッドをオーバーライドしない限り、終了処理のために Object 派生した型を Finalize マークしません。

型がメソッドを Finalize オーバーライドする場合、ガベージ コレクターは、型の各インスタンスのエントリを、最終処理キューと呼ばれる内部構造に追加します。 ファイナライズ キューには、ガベージ コレクターがメモリを再利用する前に最終処理コードを実行する必要がある、マネージド ヒープ内のすべてのオブジェクトのエントリが含まれています。 ガベージ コレクターは、次の条件下でメソッドを Finalize 自動的に呼び出します。

  • オブジェクトがメソッドの呼び出しによって最終処理から除外されていない限り、ガベージ コレクターがオブジェクトにアクセスできないことを検出した GC.SuppressFinalize 後。
  • .NET Framework の場合のみ、アプリケーションのシャットダウン中はメインオブジェクトが最終処理から除外されない限り実行されます。 シャットダウン中に、まだアクセス可能なオブジェクトも最終処理されます。

Finalizeは、特定のインスタンスで 1 回だけ自動的に呼び出されます。ただし、オブジェクトがメカニズムをGC.ReRegisterForFinalizeGC.SuppressFinalize使用して再登録され、メソッドが後で呼び出されていない場合を除きます。

Finalize 操作には、次の制限があります。

  • ファイナライザーが実行される正確な時刻は未定義です。 クラスのインスタンスのリソースを確実に確定的に解放するには、メソッドを Close 実装するか、実装を IDisposable.Dispose 提供します。
  • 2 つのオブジェクトのファイナライザーは、一方のオブジェクトが他方を参照している場合でも、特定の順序で実行される保証はありません。 つまり、オブジェクト A にオブジェクト B への参照があり、両方にファイナライザーがある場合、オブジェクト B はオブジェクト A のファイナライザーの開始時に既にファイナライズ済みである可能性があります。
  • ファイナライザーが実行されるスレッドは指定されていません。

このメソッドは Finalize 、次の例外的な状況では、完了まで実行されないか、まったく実行されない可能性があります。

  • 別のファイナライザーが無期限にブロックする場合 (無限ループに入り、決して取得できないロックを取得しようとするなど)。 ランタイムはファイナライザーを実行して完了を試みるので、ファイナライザーが無期限にブロックした場合、他のファイナライザーが呼び出されない可能性があります。
  • ランタイムにクリーンする機会を与えずにプロセスが終了した場合。 この場合、ランタイムの最初のプロセス終了通知はDLL_PROCESS_DETACH通知です。

ランタイムは、終了可能なオブジェクトの数が減少し続けている間のみ、シャットダウン中にオブジェクトの最終処理を続行します。

オーバーライドによって例外がスローされ、ランタイムが既定のポリシーをオーバーライドするアプリケーションによってホストされていない場合Finalize、ランタイムはプロセスを終了し、アクティブなtry/finallyブロックやファイナライザーは実行されません。Finalize この動作により、ファイナライザーがリソースを解放または破棄できない場合に、プロセスの整合性が確保されます。

Finalize メソッドのオーバーライド

ファイル ハンドルや、それらを使用するマネージド オブジェクトがガベージ コレクション中にカード解除されたときに解放する必要があるデータベース接続など、アンマネージ リソースを使用するクラスをオーバーライドFinalizeする必要があります。 ガベージ コレクターはマネージド リソースを自動的に解放するため、 Finalize マネージド オブジェクトのメソッドを実装しないでください。

重要

アンマネージ リソースを SafeHandle ラップするオブジェクトが使用可能な場合は、オーバーライドではなく Finalize、安全なハンドルを使用して dispose パターンを実装することをお勧めします。 詳細については、「セーフハンドルの代替セクション」を参照してください

このメソッドは Object.Finalize 既定では何も行いませんが、必要な場合にのみオーバーライド Finalize し、アンマネージ リソースのみを解放する必要があります。 少なくとも 2 つのガベージ コレクションが必要なため、終了処理を実行する場合、メモリの再利用に時間がかかる傾向があります。 さらに、参照型のみのメソッドを Finalize オーバーライドする必要があります。 共通言語ランタイムは、参照型のみを最終処理します。 値型のファイナライザーは無視されます。

メソッドの Object.Finalize スコープは protected. クラス内でこのメソッドをオーバーライドする場合は、このスコープの範囲を維持する必要があります。 メソッドを Finalize 保護することで、アプリケーションのユーザーがオブジェクトのメソッドを直接呼び出すの Finalize を防ぐことができます。

派生型のすべての実装 Finalize では、その基本型の実装 Finalizeを呼び出す必要があります。 これは、アプリケーション コードの呼び出し Finalizeが許可されている唯一のケースです。 オブジェクトの Finalize メソッドは、基底クラス以外のオブジェクトに対してメソッドを呼び出さないでください。 これは、共通言語ランタイムがシャットダウンされる場合など、呼び出し元のオブジェクトと呼び出された別のオブジェクトが同時にガベージ コレクションされることがあるからです。

Note

C# コンパイラでは、メソッドをオーバーライド Finalize できません。 代わりに、クラスのデストラクターを実装してファイナライザーを提供します。 C# デストラクターは、その基底クラスのデストラクターを自動的に呼び出します。

Visual C++ には、メソッドを実装 Finalize するための独自の構文も用意されています。 詳細については、「方法: クラスと構造体を定義して使用する (C++/CLI)」の「デストラクターとファイナライザー」セクションを参照してください。

ガベージ コレクションは非決定論的であるため、ガベージ コレクターが最終処理をいつ実行するかを正確に把握することはできません。 リソースをすぐに解放するには、Dispose パターンIDisposableインターフェイスを実装することもできます。 実装は IDisposable.Dispose 、クラスのコンシューマーがアンマネージ リソースを解放するために呼び出すことができます。また、メソッドが呼び出されない場合は、そのメソッドを使用 Finalize してアンマネージ リソースを Dispose 解放できます。

Finalizeは、ガベージ コレクション中にオブジェクトがクリーンされた後にオブジェクトを再取得する (つまり、オブジェクトを再びアクセスできるようにする) など、ほぼすべてのアクションを実行できます。 ただし、オブジェクトは 1 回だけ復活できます。 Finalize は、ガベージ コレクション中に復活したオブジェクトに対して呼び出すことができません。

代替手段としての SafeHandle

信頼性の高いファイナライザーの作成は、多くの場合、アプリケーションの状態に関する想定を行うことができないため、また、ファイナライザーの終了などのOutOfMemoryExceptionStackOverflowExceptionハンドルされないシステム例外のため、困難です。 クラスのファイナライザーを実装してアンマネージ リソースを解放する代わりに、クラスから System.Runtime.InteropServices.SafeHandle 派生したオブジェクトを使用してアンマネージ リソースをラップし、ファイナライザーなしで dispose パターンを実装できます。 .NET Framework は、次のクラスから Microsoft.Win32 派生した System.Runtime.InteropServices.SafeHandle名前空間を提供します。

次の例では、メソッドを オーバーライドする代わりに、安全なハンドルを持つ dispose パターンFinalize 使用します。 特定のファイル拡張子を FileAssociation 持つファイルを処理するアプリケーションに関するレジストリ情報をラップするクラスを定義します。 Windows RegOpenKeyEx 関数呼び出しによってパラメーターとしてout返される 2 つのレジストリ ハンドルがコンストラクターにSafeRegistryHandle渡されます。 その後、型の保護された Dispose メソッドがメソッドを SafeRegistryHandle.Dispose 呼び出して、これら 2 つのハンドルを解放します。

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(disposing: 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();
      }
   }
}
open Microsoft.Win32.SafeHandles
open System
open System.ComponentModel
open System.IO
open System.Runtime.InteropServices

// Windows API constants.
let HKEY_CLASSES_ROOT = 0x80000000
let ERROR_SUCCESS = 0
let KEY_QUERY_VALUE = 1
let KEY_SET_VALUE = 0x2
let REG_SZ = 1u
let MAX_PATH = 260

// Windows API calls.
[<DllImport("advapi32.dll", CharSet= CharSet.Auto, SetLastError=true)>]
extern int RegOpenKeyEx(nativeint hKey, string lpSubKey, int ulOptions, int samDesired, nativeint& phkResult)
[<DllImport("advapi32.dll", CharSet= CharSet.Unicode, EntryPoint = "RegQueryValueExW", SetLastError=true)>]
extern int RegQueryValueEx(nativeint hKey, string lpValueName, int lpReserved, uint& lpType, string lpData, uint& lpcbData)
[<DllImport("advapi32.dll", SetLastError = true)>]
extern int RegSetValueEx(nativeint hKey, [<MarshalAs(UnmanagedType.LPStr)>] string lpValueName, int Reserved, uint dwType, [<MarshalAs(UnmanagedType.LPStr)>] string lpData, int cpData)
[<DllImport("advapi32.dll", SetLastError=true)>]
extern int RegCloseKey(unativeint hKey)

type FileAssociationInfo(fileExtension: string) =
    // Private values.
    let ext =
        if fileExtension.StartsWith "." |> not then
            "." + fileExtension
        else
            fileExtension
    let mutable args = ""
    let mutable hAppIdHandle = Unchecked.defaultof<SafeRegistryHandle>
    let mutable hExtHandle = Unchecked.defaultof<SafeRegistryHandle>
    let openCmd = 
        let mutable lpType = 0u
        let mutable hExtension = 0n
        // Get the file extension value.
        let retVal = RegOpenKeyEx(nativeint HKEY_CLASSES_ROOT, fileExtension, 0, KEY_QUERY_VALUE, &hExtension)
        if retVal <> ERROR_SUCCESS then
            raise (Win32Exception retVal)
        // Instantiate the first SafeRegistryHandle.
        hExtHandle <- new SafeRegistryHandle(hExtension, true)

        let appId = String(' ', MAX_PATH)
        let mutable appIdLength = uint appId.Length
        let retVal = RegQueryValueEx(hExtHandle.DangerousGetHandle(), String.Empty, 0, &lpType, appId, &appIdLength)
        if retVal <> ERROR_SUCCESS then
            raise (Win32Exception retVal)
        // We no longer need the hExtension handle.
        hExtHandle.Dispose()

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

        // Open the application identifier key.
        let exeName = String(' ', MAX_PATH)
        let exeNameLength = uint exeName.Length
        let mutable hAppId = 0n
        let retVal = RegOpenKeyEx(nativeint HKEY_CLASSES_ROOT, appId, 0, KEY_QUERY_VALUE ||| KEY_SET_VALUE, &hAppId)
        if retVal <> ERROR_SUCCESS then
            raise (Win32Exception retVal)

        // Instantiate the second SafeRegistryHandle.
        hAppIdHandle <- new SafeRegistryHandle(hAppId, true)

        // Get the executable name for this file type.
        let exePath = String(' ', MAX_PATH)
        let mutable exePathLength = uint exePath.Length
        let retVal = RegQueryValueEx(hAppIdHandle.DangerousGetHandle(), String.Empty, 0, &lpType, exePath, &exePathLength)
        if retVal <> ERROR_SUCCESS then
            raise (Win32Exception retVal)

        // Determine the number of characters without the terminating null.
        let exePath = 
            exePath.Substring(0, int exePathLength / 2 - 1)
            // Remove any environment strings.
            |> Environment.ExpandEnvironmentVariables

        let position = exePath.IndexOf '%'
        if position >= 0 then
            args <- exePath.Substring position
            // Remove command line parameters ('%0', etc.).
            exePath.Substring(0, position).Trim()
        else
            exePath

    member _.Extension =
        ext

    member _.Open
        with get () = openCmd
        and set (value) =
            if hAppIdHandle.IsInvalid || hAppIdHandle.IsClosed then
                raise (InvalidOperationException "Cannot write to registry key.")
            if not (File.Exists value) then
                raise (FileNotFoundException $"'{value}' does not exist")
            
            let cmd = value + " %1"
            let retVal = RegSetValueEx(hAppIdHandle.DangerousGetHandle(), String.Empty, 0, REG_SZ, value, value.Length + 1)
            if retVal <> ERROR_SUCCESS then
                raise (Win32Exception retVal)

    member this.Dispose() =
        this.Dispose true
        GC.SuppressFinalize this

    member _.Dispose(disposing) =
        // Ordinarily, we release unmanaged resources here
        // but all are wrapped by safe handles.

        // Release disposable objects.
        if disposing then
           if hExtHandle <> null then hExtHandle.Dispose()
           if hAppIdHandle <> null then hAppIdHandle.Dispose()

    interface IDisposable with
        member this.Dispose() =
            this.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(disposing:=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