この記事では、この API のリファレンス ドキュメントに補足的な解説を提供します。
Finalize メソッドは、オブジェクトが破棄される前に、現在のオブジェクトによって保持されているアンマネージ リソースに対してクリーンアップ操作を実行するために使用されます。 メソッドは保護されているため、このクラスまたは派生クラスを介してのみアクセスできます。
最終処理のしくみ
Object クラスは、Finalize メソッドの実装を提供しません。ガベージ コレクターは、Finalize メソッドをオーバーライドしない限り、Objectから派生した型を最終処理にマークしません。
型が Finalize メソッドをオーバーライドする場合、ガベージ コレクターは、型の各インスタンスのエントリを、最終処理キューと呼ばれる内部構造体に追加します。 ファイナライズ キューには、ガベージ コレクターがメモリを再利用する前に最終処理コードを実行する必要がある、マネージド ヒープ内のすべてのオブジェクトのエントリが含まれています。 その後、ガベージ コレクターは、次の条件下で Finalize メソッドを自動的に呼び出します。
- オブジェクトが GC.SuppressFinalize メソッドの呼び出しによって最終処理から除外されていない限り、ガベージ コレクターがオブジェクトにアクセスできないことを検出した後。
- .NET Framework でのみ、オブジェクトが最終処理から除外されない限り、アプリケーション ドメインのシャットダウン中。 シャットダウン中に、まだアクセス可能なオブジェクトも最終処理されます。
Finalize は、オブジェクトが GC.ReRegisterForFinalize などのメカニズムを使用して再登録され、 GC.SuppressFinalize メソッドが後で呼び出されない限り、特定のインスタンスで 1 回だけ自動的に呼び出されます。
Finalize 操作には、次の制限があります。
- ファイナライザーが実行される正確な時刻は未定義です。 クラスのインスタンスのリソースを確実に確定的に解放するには、
Close
メソッドを実装するか、 IDisposable.Dispose 実装を提供します。 - 2 つのオブジェクトのファイナライザーは、一方のオブジェクトが他方を参照している場合でも、特定の順序で実行される保証はありません。 つまり、オブジェクト A にオブジェクト B への参照があり、両方にファイナライザーがある場合、オブジェクト B はオブジェクト A のファイナライザーの開始時に既にファイナライズ済みである可能性があります。
- ファイナライザーが実行されるスレッドは指定されていません。
Finalize メソッドは、次の例外的な状況では、完了まで実行されないか、まったく実行されない可能性があります。
- 別のファイナライザーが無期限にブロックする場合 (無限ループに入り、決して取得できないロックを取得しようとするなど)。 ランタイムはファイナライザーを実行して完了を試みるので、ファイナライザーが無期限にブロックした場合、他のファイナライザーが呼び出されない可能性があります。
- ランタイムにクリーンアップの機会を与えずにプロセスが終了した場合。 この場合、ランタイムの最初のプロセス終了通知はDLL_PROCESS_DETACH通知です。
ランタイムは、終了可能なオブジェクトの数が減少し続けている間のみ、シャットダウン中にオブジェクトの最終処理を続行します。
FinalizeまたはFinalizeのオーバーライドによって例外がスローされ、ランタイムが既定のポリシーをオーバーライドするアプリケーションによってホストされていない場合、ランタイムはプロセスを終了し、アクティブなtry
/finally
ブロックまたはファイナライザーは実行されません。 この動作により、ファイナライザーがリソースを解放または破棄できない場合に、プロセスの整合性が確保されます。
Finalize メソッドのオーバーライド
ファイル ハンドルやデータベース接続など、アンマネージ リソースを使用するクラスの Finalize をオーバーライドする必要があります。このクラスを使用するマネージド オブジェクトがガベージ コレクション中に破棄されるときに解放する必要があります。 ガベージ コレクターはマネージド リソースを自動的に解放するため、マネージド オブジェクトの Finalize メソッドを実装しないでください。
Von Bedeutung
アンマネージ リソースをラップする SafeHandle オブジェクトが使用可能な場合は、安全なハンドルを使用して dispose パターンを実装し、 Finalizeをオーバーライドしないことをお勧めします。 詳細については、 SafeHandle の代替 セクションを参照してください。
Object.Finalize メソッドは既定では何も行いませんが、Finalizeは必要な場合にのみオーバーライドし、アンマネージド リソースのみを解放する必要があります。 少なくとも 2 つのガベージ コレクションが必要なため、終了処理を実行する場合、メモリの再利用に時間がかかる傾向があります。 また、参照型の場合にのみ、 Finalize メソッドをオーバーライドする必要があります。 共通言語ランタイムは、参照型のみを最終処理します。 値型のファイナライザーは無視されます。
Object.Finalize メソッドのスコープはprotected
。 クラス内のメソッドをオーバーライドするときは、この制限付きスコープを維持する必要があります。
Finalize メソッドを保護することで、アプリケーションのユーザーがオブジェクトのFinalize メソッドを直接呼び出さないようにすることができます。
派生型の Finalize のすべての実装では、その基本型の Finalize の実装を呼び出す必要があります。 これは、アプリケーション コードが Finalizeを呼び出す唯一のケースです。 オブジェクトの Finalize メソッドは、基底クラス以外のオブジェクトに対してメソッドを呼び出さないでください。 これは、共通言語ランタイムのシャットダウンの場合など、呼び出される他のオブジェクトが呼び出し元のオブジェクトと同時に収集される可能性があるためです。
注
C# コンパイラでは、 Finalize メソッドをオーバーライドできません。 代わりに、クラスの デストラクター を実装してファイナライザーを提供します。 C# デストラクターは、その基底クラスのデストラクターを自動的に呼び出します。
Visual C++ には、 Finalize メソッドを実装するための独自の構文も用意されています。 詳細については、「 方法: クラスと構造体を定義して使用する (C++/CLI)」の「デストラクターとファイナライザー」セクションを参照してください。
ガベージ コレクションは非決定論的であるため、ガベージ コレクターが最終処理をいつ実行するかを正確に把握することはできません。 リソースをすぐに解放するには、 dispose パターン と IDisposable インターフェイスを実装することもできます。 IDisposable.Dispose実装は、クラスのコンシューマーがアンマネージ リソースを解放するために呼び出すことができます。また、Dispose メソッドが呼び出されない場合は、Finalize メソッドを使用してアンマネージ リソースを解放できます。
Finalize は、ガベージ コレクション中にクリーンアップされた後にオブジェクトを再取得する (つまり、オブジェクトを再びアクセスできるようにする) など、ほぼすべてのアクションを実行できます。 ただし、オブジェクトは 1 回だけ復活できます。 Finalize は、ガベージ コレクション中に復活したオブジェクトに対して呼び出すことはできません。
SafeHandle の代替
信頼性の高いファイナライザーの作成は、多くの場合、アプリケーションの状態を想定できないため、また、 OutOfMemoryException や StackOverflowException などのハンドルされないシステム例外によってファイナライザーが終了するため、困難です。 クラスのファイナライザーを実装してアンマネージ リソースを解放する代わりに、 System.Runtime.InteropServices.SafeHandle クラスから派生したオブジェクトを使用してアンマネージ リソースをラップし、ファイナライザーなしで dispose パターンを実装できます。 .NET Framework は、System.Runtime.InteropServices.SafeHandleから派生したMicrosoft.Win32名前空間に次のクラスを提供します。
- SafeFileHandle は、ファイル ハンドルのラッパー クラスです。
- SafeMemoryMappedFileHandle は、メモリ マップト ファイル ハンドルのラッパー クラスです。
- SafeMemoryMappedViewHandle は、アンマネージ メモリのブロックへのポインターのラッパー クラスです。
- SafeNCryptKeyHandle、 SafeNCryptProviderHandle、および SafeNCryptSecretHandle は、暗号化ハンドルのラッパー クラスです。
- SafePipeHandle はパイプ ハンドルのラッパー クラスです。
- SafeRegistryHandle は、レジストリ キーへのハンドルのラッパー クラスです。
- SafeWaitHandle は待機ハンドルのラッパー クラスです。
次の例では、Finalize メソッドをオーバーライドする代わりに、安全なハンドルで dispose パターンを使用します。 特定のファイル拡張子を持つファイルを処理するアプリケーションに関するレジストリ情報をラップする 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
.NET