實作 Finalize 和 Dispose 以清除 Unmanaged 資源
注意事項 |
---|
關於最後確定和處理使用 c + + 的資源的資訊,請參閱Destructors and Finalizers in Visual C++。 |
類別執行個體通常透過非執行階段所管理的資源,例如視窗控制代碼 (HWND)、資料庫連接等等,來封裝控制項。 因此,您應該提供明確和隱含的方式來釋放這些資源。 請藉由在物件上實作受保護的 Finalize,來提供隱含控制 (C# 和 C++ 中為解構函式語法)。 在不再有任何有效的物件參考之後,記憶體回收行程會某些時間點上呼叫這個方法。
在某些情況下,您可能想要提供程式設計人員在記憶體回收行程釋放物件之前,使用能夠明確釋放這些外部資源的物件。 如果外部資源不足或太過消耗,若程式設計人員明確釋放不再被使用的資源,會達到更佳的效能。 若要提供明確控制,請實作 IDisposable 提供的 Dispose。 物件的消費者應該在完成此物件的使用時呼叫此方法。 即使此物件的其他參考仍然存在,還是可以呼叫 Dispose。
請注意,即使當您使用 Dispose 來提供明確控制時,也應該使用 Finalize 方法提供隱含清除。 如果程式設計人員呼叫 Dispose 失敗,則 Finalize 會提供備份,以避免資源被永久遺漏。
如需實作 Finalize 和 Dispose 來清除 Unmanaged 資源的詳細資訊,請參閱記憶體回收。 下麵的示例闡釋的基本設計模式,實施處置。 這個範例需要 System 命名空間。
' Design pattern for a base class.
Public Class Base
Implements IDisposable
' Field to handle multiple calls to Dispose gracefully.
Dim disposed as Boolean = false
' Implement IDisposable.
Public Overloads Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Overloads Overridable Sub Dispose(disposing As Boolean)
If disposed = False Then
If disposing Then
' Free other state (managed objects).
disposed = True
End If
' Free your own state (unmanaged objects).
' Set large fields to null.
End If
End Sub
Protected Overrides Sub Finalize()
' Simply call Dispose(False).
Dispose (False)
End Sub
End Class
' Design pattern for a derived class.
Public Class Derived
Inherits Base
' Field to handle multiple calls to Dispose gracefully.
Dim disposed as Boolean = false
Protected Overloads Overrides Sub Dispose(disposing As Boolean)
If disposed = False Then
If disposing Then
' Release managed resources.
End If
' Release unmanaged resources.
' Set large fields to null.
disposed = True
End If
' Call Dispose on your base class.
Mybase.Dispose(disposing)
End Sub
' The derived class does not have a Finalize method
' or a Dispose method without parameters because it inherits
' them from the base class.
End Class
// Design pattern for a base class.
public class Base: IDisposable
{
private bool disposed = false;
//Implement IDisposable.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Free other state (managed objects).
}
// Free your own state (unmanaged objects).
// Set large fields to null.
disposed = true;
}
}
// Use C# destructor syntax for finalization code.
~Base()
{
// Simply call Dispose(false).
Dispose (false);
}
}
// Design pattern for a derived class.
public class Derived: Base
{
private bool disposed = false;
protected override void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Release managed resources.
}
// Release unmanaged resources.
// Set large fields to null.
// Call Dispose on your base class.
disposed = true;
}
base.Dispose(disposing);
}
// The derived class does not have a Finalize method
// or a Dispose method without parameters because it inherits
// them from the base class.
}
下麵的代碼將展開前面的示例中顯示的不同方式處置 調用時 Finalize調用。 處理模式的階段跟蹤輸出到主控台。 在派生類中處理的分配和釋放非託管資源。
Imports System
Imports System.Collections.Generic
Imports System.Runtime.InteropServices
' Design pattern for a base class.
Public MustInherit Class Base
Implements IDisposable
Private disposed as Boolean = false
Private instName As String
Private trackingList As List(Of Object)
Public Sub New(instanceName As String, tracking As List(Of Object))
MyClass.instName = instanceName
trackingList = tracking
trackingList.Add(Me)
End Sub
Public ReadOnly Property InstanceName() As String
Get
Return instName
End Get
End Property
'Implement IDisposable.
Public Overloads Sub Dispose() Implements IDisposable.Dispose
Console.WriteLine(vbNewLine + "[{0}].Base.Dispose()", instName)
Dispose(true)
GC.SuppressFinalize(Me)
End Sub
Protected Overloads Overridable Sub Dispose(disposing As Boolean)
If disposed = False Then
If disposing Then
' Free other state (managed objects).
Console.WriteLine("[{0}].Base.Dispose(true)", instName)
trackingList.Remove(Me)
Console.WriteLine("[{0}] Removed from tracking list: {1:x16}",
instanceName, MyClass.GetHashCode())
Else
Console.WriteLine("[{0}].Base.Dispose(false)", instName)
End If
disposed = True
End If
End Sub
Protected Overrides Sub Finalize()
' Simply call Dispose(False).
Console.WriteLine(vbNewLine + "[{0}].Base.Finalize()", instName)
Dispose(False)
End Sub
End Class
' Design pattern for a derived class.
Public Class Derived
Inherits Base
Private disposed as Boolean = false
Private umResource As IntPtr
Public Sub New(instanceName As String, tracking As List(Of Object))
MyBase.New(instanceName, tracking)
' Save the instance name as an unmanaged resource
umResource = Marshal.StringToCoTaskMemAuto(instanceName)
End Sub
Protected Overloads Overrides Sub Dispose(disposing As Boolean)
If disposed = False Then
If disposing Then
Console.WriteLine("[{0}].Derived.Dispose(true)", InstanceName)
' Release managed resources.
Else
Console.WriteLine("[{0}].Derived.Dispose(false)", InstanceName)
End If
' Release unmanaged resources.
If umResource <> IntPtr.Zero
Marshal.FreeCoTaskMem(umResource)
Console.WriteLine("[{0}] Unmanaged memory freed at {1:x16}", _
InstanceName, umResource.ToInt64())
umResource = IntPtr.Zero
End If
disposed = True
End If
' Call Dispose in the base class.
MyBase.Dispose(disposing)
End Sub
' The derived class does not have a Finalize method
' or a Dispose method without parameters because it inherits
' them from the base class.
End Class
Public Class TestDisposal
Public Shared Sub Main()
Dim tracking As New List(Of Object)()
' Dispose is not called, Finalize will be called later.
Using Nothing
Console.WriteLine(vbNewLine + "Disposal Scenario: #1" + vbNewLine)
Dim d3 As New Derived("d1", tracking)
End Using
' Dispose is implicitly called in the scope of the using statement.
Using d1 As New Derived("d2", tracking)
Console.WriteLine(vbNewLine + "Disposal Scenario: #2" + vbNewLine)
End Using
' Dispose is explicitly called.
Using Nothing
Console.WriteLine(vbNewLine + "Disposal Scenario: #3" + vbNewLine)
Dim d2 As New Derived("d3", tracking)
d2.Dispose()
End Using
' Again, Dispose is not called, Finalize will be called later.
Using Nothing
Console.WriteLine(vbNewLine + "Disposal Scenario: #4" + vbNewLine)
Dim d4 As New Derived("d4", tracking)
End Using
' List the objects remaining to dispose.
Console.WriteLine(vbNewLine + "Objects remaining to dispose = {0:d}", tracking.Count)
For Each dd As Derived in tracking
Console.WriteLine(" Reference Object: {0:s}, {1:x16}",
dd.InstanceName, dd.GetHashCode())
Next dd
' Queued finalizers will be exeucted when Main() goes out of scope.
Console.WriteLine(vbNewLine + "Dequeueing finalizers...")
End Sub
End Class
' The program will display output similar to the following:
'
' Disposal Scenario: #1
'
'
' Disposal Scenario: #2
'
'
' [d2].Base.Dispose()
' [d2].Derived.Dispose(true)
' [d2] Unmanaged memory freed at 00000000001ce420
' [d2].Base.Dispose(true)
' [d2] Removed from tracking list: 0000000002bf8098
'
' Disposal Scenario: #3
'
'
' [d3].Base.Dispose()
' [d3].Derived.Dispose(true)
' [d3] Unmanaged memory freed at 00000000001ce420
' [d3].Base.Dispose(true)
' [d3] Removed from tracking list: 0000000000bb8560
'
' Disposal Scenario: #4
'
'
' Objects remaining to dispose = 2
' Reference Object: d1, 000000000297b065
' Reference Object: d4, 0000000003553390
'
' Dequeueing finalizers...
'
' [d4].Base.Finalize()
' [d4].Derived.Dispose(false)
' [d4] Unmanaged memory freed at 00000000001ce420
' [d4].Base.Dispose(false)
'
' [d1].Base.Finalize()
' [d1].Derived.Dispose(false)
' [d1] Unmanaged memory freed at 00000000001ce3f0
' [d1].Base.Dispose(false)
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
// Design pattern for a base class.
public abstract class Base : IDisposable
{
private bool disposed = false;
private string instanceName;
private List<object> trackingList;
public Base(string instanceName, List<object> tracking)
{
this.instanceName = instanceName;
trackingList = tracking;
trackingList.Add(this);
}
public string InstanceName
{
get
{
return instanceName;
}
}
//Implement IDisposable.
public void Dispose()
{
Console.WriteLine("\n[{0}].Base.Dispose()", instanceName);
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Free other state (managed objects).
Console.WriteLine("[{0}].Base.Dispose(true)", instanceName);
trackingList.Remove(this);
Console.WriteLine("[{0}] Removed from tracking list: {1:x16}",
instanceName, this.GetHashCode());
}
else
{
Console.WriteLine("[{0}].Base.Dispose(false)", instanceName);
}
disposed = true;
}
}
// Use C# destructor syntax for finalization code.
~Base()
{
// Simply call Dispose(false).
Console.WriteLine("\n[{0}].Base.Finalize()", instanceName);
Dispose(false);
}
}
// Design pattern for a derived class.
public class Derived : Base
{
private bool disposed = false;
private IntPtr umResource;
public Derived(string instanceName, List<object> tracking) :
base(instanceName, tracking)
{
// Save the instance name as an unmanaged resource
umResource = Marshal.StringToCoTaskMemAuto(instanceName);
}
protected override void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
Console.WriteLine("[{0}].Derived.Dispose(true)", InstanceName);
// Release managed resources.
}
else
{
Console.WriteLine("[{0}].Derived.Dispose(false)", InstanceName);
}
// Release unmanaged resources.
if (umResource != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(umResource);
Console.WriteLine("[{0}] Unmanaged memory freed at {1:x16}",
InstanceName, umResource.ToInt64());
umResource = IntPtr.Zero;
}
disposed = true;
}
// Call Dispose in the base class.
base.Dispose(disposing);
}
// The derived class does not have a Finalize method
// or a Dispose method without parameters because it inherits
// them from the base class.
}
public class TestDisposal
{
public static void Main()
{
List<object> tracking = new List<object>();
// Dispose is not called, Finalize will be called later.
using (null)
{
Console.WriteLine("\nDisposal Scenario: #1\n");
Derived d3 = new Derived("d1", tracking);
}
// Dispose is implicitly called in the scope of the using statement.
using (Derived d1 = new Derived("d2", tracking))
{
Console.WriteLine("\nDisposal Scenario: #2\n");
}
// Dispose is explicitly called.
using (null)
{
Console.WriteLine("\nDisposal Scenario: #3\n");
Derived d2 = new Derived("d3", tracking);
d2.Dispose();
}
// Again, Dispose is not called, Finalize will be called later.
using (null)
{
Console.WriteLine("\nDisposal Scenario: #4\n");
Derived d4 = new Derived("d4", tracking);
}
// List the objects remaining to dispose.
Console.WriteLine("\nObjects remaining to dispose = {0:d}", tracking.Count);
foreach (Derived dd in tracking)
{
Console.WriteLine(" Reference Object: {0:s}, {1:x16}",
dd.InstanceName, dd.GetHashCode());
}
// Queued finalizers will be exeucted when Main() goes out of scope.
Console.WriteLine("\nDequeueing finalizers...");
}
}
// The program will display output similar to the following:
//
// Disposal Scenario: #1
//
//
// Disposal Scenario: #2
//
//
// [d2].Base.Dispose()
// [d2].Derived.Dispose(true)
// [d2] Unmanaged memory freed at 000000000034e420
// [d2].Base.Dispose(true)
// [d2] Removed from tracking list: 0000000002bf8098
//
// Disposal Scenario: #3
//
//
// [d3].Base.Dispose()
// [d3].Derived.Dispose(true)
// [d3] Unmanaged memory freed at 000000000034e420
// [d3].Base.Dispose(true)
// [d3] Removed from tracking list: 0000000000bb8560
//
// Disposal Scenario: #4
//
//
// Objects remaining to dispose = 2
// Reference Object: d1, 000000000297b065
// Reference Object: d4, 0000000003553390
//
// Dequeueing finalizers...
//
// [d4].Base.Finalize()
// [d4].Derived.Dispose(false)
// [d4] Unmanaged memory freed at 000000000034e420
// [d4].Base.Dispose(false)
//
// [d1].Base.Finalize()
// [d1].Derived.Dispose(false)
// [d1] Unmanaged memory freed at 000000000034e3f0
// [d1].Base.Dispose(false)
說明的設計模式,實施額外的代碼示例為Finalize和 處置,請參閱 實現 Dispose 方法。
自訂 Dispose 方法名稱
有時候定義域特定的名稱要比 Dispose 更適合。 例如,檔案封裝可能要使用方法名稱 Close。 在這種情況下,會私用地實作 Dispose,以及建立可呼叫 Dispose 的公用 Close 方法。 下列程式碼範例說明這個模式。 您可以用適合於範圍的名稱取代 Close。 這個範例需要 System 命名空間。
' Do not make this method overridable.
' A derived class should not be allowed
' to override this method.
Public Sub Close()
' Call the Dispose method with no parameters.
Dispose()
End Sub
// Do not make this method virtual.
// A derived class should not be allowed
// to override this method.
public void Close()
{
// Call the Dispose method with no parameters.
Dispose();
}
Finalize
下列規則概述 Finalize 方法的用法方針:
只在需要最終化的物件上實作 Finalize。 有與 Finalize 方法關聯的效能成本。
如果您需要 Finalize 方法,應考慮實作 IDisposable,讓類別的使用者得以避免發生叫用 Finalize 方法的成本。
不要使 Finalize 方法更加顯露。 它應該是 protected,而非 public。
物件的 Finalize 方法應該釋放其擁有的任何外部資源。 此外,Finalize 方法應該只釋放物件所包含的資源。 Finalize 方法不應該參考任何其他物件。
除了物件的基底類別之外,不要直接在物件上呼叫 Finalize。 這不是 C# 程式語言的有效作業。
從物件的 Finalize 方法呼叫基底類別的 Finalize 方法。
注意事項 會使用 C# 和 C++ 解構函式語法來自動呼叫基底類別的 Finalize 方法。
處置
下列規則概述 Dispose 方法的使用方針:
在型別上實作處置設計模式,該型別會封裝需要明確釋放的資源。 使用者可以呼叫公用 Dispose 方法來釋放外部資源。
在基底型別上實作處置設計模式,此型別一般都有包含資源的衍生型別,即使當基底型別並沒有時亦然。 如果基底型別有 Close 方法,則通常表示需要實作 Dispose。 在這種情況下,不要在基底型別上實作 Finalize 方法。 Finalize 應該在任何引入需要清除的資源的衍生型別上被實作。
釋放型別在其 Dispose 方法中所擁有的任何可處置資源。
已經在執行個體上呼叫 Dispose 之後,藉由呼叫 GC.SuppressFinalize 來避免 Finalize 方法執行。 這個規則的例外情況是必須在不是 Dispose 所涵蓋的 Finalize 中完成工作的罕見狀況。
如果基底類別會實作 IDisposable,則呼叫它的 Dispose 方法。
不要假設 Dispose 將會被呼叫。 型別所擁有的 Unmanaged 資源也應該在不呼叫 Dispose 的事件之 Finalize 方法中被釋放。
當資源已被處置時,從這個型別的執行個體方法 (除了 Dispose 以外) 擲回 ObjectDisposedException 。 這個規則不適用於 Dispose 方法,因為這個方法應該可以被呼叫多次而不擲回例外狀況。
透過基底型別的階層架構將呼叫傳播到 Dispose。 Dispose 方法應該釋放這個物件所包含的全部資源,以及這個物件所擁有的任何物件。 例如,您可以建立類似 TextReader 的物件,其中包含 Stream 和 Encoding,兩者都是由 TextReader 所建立,而不需要知道使用者。 此外,Stream 和 Encoding 都可以取得外部資源。 當您在 TextReader 上呼叫 Dispose 方法時,它應該依次在 Stream 和 Encoding 上呼叫 Dispose,使它們釋放其外部資源。
您應該考慮在呼叫某物件的 Dispose 方法之後,不允許使用該物件。 重新建立已經被處置的物件是一種很難實作的模式。
允許呼叫 Dispose 方法多次,而不會擲回例外狀況。 在第一次呼叫之後,方法應該不會做任何動作。
Portions Copyright 2005 Microsoft Corporation. All rights reserved.
Portions Copyright Addison-Wesley Corporation. All rights reserved.
設計指引的詳細資訊,請參閱"框架設計準則:公約、 成語和可重複使用的模式。網路圖書館"書 Krzysztof Cwalina 和布拉德 · 艾布拉姆斯,2005年艾迪生 - 衛斯理,發表。