Korrekte Implementierung von IDisposable
Die zum Freigeben von Objektressourcen in .NET verwendete Schnittstelle ist IDisposable, aber die Implementierung dieser Schnittstelle ist recht speziell. Darüber hinaus gibt es zwei Funktionen: Finalize und Close.
MSDN empfiehlt die Implementierung der IDisposable-Schnittstelle nach dem folgenden Muster:
1 öffentliche Klasse Foo: IDisposable
2 {
3 public void Dispose()
4 {
5 Dispose(true);
6 GC.SuppressFinalize(this);
7}
8
9 protected virtual void Dispose(bool disposing)
10 {
11 if (!m_disposed)
12 {
13 wenn (entsorgen)
14 {
15 // Verwaltete Ressourcen freigeben
16}
17
18 // Nicht verwaltete Ressourcen freigeben
19
20 m_disposed = true;
einundzwanzig }
zweiundzwanzig }
dreiundzwanzig
24 ~Foo()
25 {
26 Entsorgen(false);
27}
28
29 privater Bool m_disposed;
30}
31
32
Es gibt tatsächlich zwei Funktionen zum Freigeben von Ressourcen in .NET-Objekten: Dispose und Finalize. Der Zweck von Finalize besteht darin, nicht verwaltete Ressourcen freizugeben, während Dispose dazu verwendet wird, alle Ressourcen freizugeben, einschließlich verwalteter und nicht verwalteter.
In diesem Modus verwendet die Funktion void Dispose(bool disposing) einen Dispose-Parameter, um zu unterscheiden, ob sie gerade von Dispose() aufgerufen wird. Wenn es von Dispose() aufgerufen wird, müssen sowohl verwaltete als auch nicht verwaltete Ressourcen gleichzeitig freigegeben werden. Wenn es von ~Foo() (also Finalize() von C#) aufgerufen wird, müssen Sie nur die nicht verwalteten Ressourcen freigeben.
Dies liegt daran, dass die Dispose()-Funktion explizit von anderem Code aufgerufen wird und die Freigabe von Ressourcen erfordert, während Finalize vom GC aufgerufen wird. Andere von Foo referenzierte verwaltete Objekte müssen möglicherweise nicht zerstört werden, wenn der GC aufgerufen wird, und selbst wenn sie zerstört werden, werden sie vom GC aufgerufen. Daher müssen in Finalize nur nicht verwaltete Ressourcen freigegeben werden. Da andererseits verwaltete und nicht verwaltete Ressourcen in Dispose() freigegeben wurden, ist es nicht erforderlich, Finalize erneut aufzurufen, wenn das Objekt von GC recycelt wird. Rufen Sie daher GC.SuppressFinalize(this) in Dispose() auf, um doppelte Aufrufe zu vermeiden Abschließen.
Es gibt jedoch kein Problem, selbst wenn Finalize und Dispose wiederholt aufgerufen werden, da die Variable m_disposed vorhanden ist, die Ressource nur einmal freigegeben wird und redundante Aufrufe ignoriert werden.
Daher garantiert das obige Muster:
1. Finalize gibt nur nicht verwaltete Ressourcen frei.
2. Dispose gibt verwaltete und nicht verwaltete Ressourcen frei.
3. Es ist kein Problem, Finalize und Dispose wiederholt aufzurufen.
4. Finalize und Dispose verwenden dieselbe Ressourcenfreigabestrategie .
In C# muss dieses Muster explizit implementiert werden, wobei die ~Foo()-Funktion von C# Finalize() darstellt. In C++/CLI wird dieser Modus automatisch implementiert, der C++-Klassendestruktor ist jedoch anders.
Gemäß der C++-Semantik wird der Destruktor aufgerufen, wenn er den Gültigkeitsbereich verlässt oder gelöscht wird. In Managed C++ (d. h. verwaltetem C++ in .NET 1.1) entspricht der Destruktor der Finalize()-Methode in der CLR, die vom GC während der Garbage Collection aufgerufen wird. Daher ist der Zeitpunkt des Aufrufs unklar. In C++/CLI in .NET 2.0 wurde die Semantik von Destruktoren geändert, um der Dispose()-Methode zu entsprechen, was zwei Dinge impliziert:
1. Alle CLR-Klassen in C++/CLI implementieren die Schnittstelle IDisposable, sodass Sie das Schlüsselwort using verwenden können, um auf Instanzen dieser Klasse in C# zuzugreifen.
2. Der Destruktor entspricht nicht mehr Finalize().
Was den ersten Punkt betrifft, ist das eine gute Sache, ich denke, dass Dispose() semantisch näher am C++-Destruktor liegt. Was den zweiten Punkt betrifft, hat Microsoft eine Erweiterung vorgenommen, indem es die Funktion „!“ eingeführt hat, wie unten gezeigt:
1 öffentliche Referenzklasse Foo
2 {
3 öffentlich:
4Foo();
5 ~Foo(); // Destruktor
6 !Foo(); // Finalizer
7};
8
Die „!“-Funktion (ich weiß wirklich nicht, wie ich sie nennen soll) ersetzt die ursprüngliche Finalize() in Managed C++ und wird vom GC aufgerufen. MSDN empfiehlt, Code wie diesen zu schreiben, um die Codeduplizierung zu reduzieren:
1 ~Foo()
2 {
3 //Verwaltete Ressourcen freigeben
4 this->!Foo();
5}
6
7 !Foo()
8 {
9 //Nicht verwaltete Ressourcen freigeben
10}
11
Für die obige Klasse lautet der entsprechende von C++/CLI generierte C#-Code tatsächlich wie folgt:
1 öffentliche Klasse Foo
2 {
3 private Leere !Foo()
4 {
5 // Nicht verwaltete Ressourcen freigeben
6}
7
8 private Leere ~Foo()
9 {
10 // Verwaltete Ressourcen freigeben
11 !Foo();
12}
13
14 publicFoo()
15 {
16}
17
18 public void Dispose()
19 {
20 Dispose(true);
21 GC.SuppressFinalize(this);
zweiundzwanzig }
dreiundzwanzig
24 protected virtual void Dispose(bool disposing)
25 {
26 wenn (entsorgen)
27 {
28 ~Foo();
29 }
30 sonst
31 {
32 versuchen
33 {
34 !Foo();
35}
36 endlich
37 {
38 base.Finalize();
39 }
40}
41 }
42
43 protected void Finalize()
44 {
45 Entsorgen(false);
46 }
47 }
48
Da ~Foo() und !Foo() nicht wiederholt aufgerufen werden (zumindest glaubt MS das), gibt es in diesem Code keine Variablen, die mit denen des vorherigen m_disposed identisch sind, aber die Grundstruktur ist dieselbe.
Darüber hinaus können Sie sehen, dass es sich nicht um ~Foo() und !Foo() handelt, die Dispose und Finalize sind, sondern der C++/CLI-Compiler zwei Dispose- und Finalize-Funktionen generiert und sie zum entsprechenden Zeitpunkt aufruft. C++/CLI hat tatsächlich viel Arbeit geleistet, aber das einzige Problem besteht darin, dass es darauf angewiesen ist, dass der Benutzer !Foo() in ~Foo() aufruft.
In Bezug auf die Ressourcenfreigabe ist als letztes die Funktion „Schließen“ zu erwähnen. Laut MSDN ist diese Funktion semantisch sehr ähnlich und wird bereitgestellt, um Benutzern ein angenehmeres Gefühl zu geben, da Benutzer eher daran gewöhnt sind, Close() für bestimmte Objekte, z. B. Dateien, aufzurufen.
Da diese beiden Funktionen jedoch letztendlich dasselbe bewirken, lautet der von MSDN empfohlene Code:
1 öffentliche Lücke Close()
2 {
3 Dispose(();
4}
5
6
Hier wird die Dispose-Funktion ohne Parameter direkt aufgerufen, um die gleiche Semantik wie Dispose zu erhalten. Dies scheint perfekt zu sein, aber wenn gleichzeitig „Dispose“ und „Close“ bereitgestellt werden, führt dies zu Verwirrung bei den Benutzern. Ohne die Codedetails zu sehen, ist es schwierig, den Unterschied zwischen diesen beiden Funktionen zu erkennen. Daher besagen die .NET-Codedesignspezifikationen, dass Benutzer tatsächlich nur eine dieser beiden Funktionen verwenden können. Das vorgeschlagene Muster lautet also:
1 öffentliche Klasse Foo: IDisposable
2 {
3 public void Close()
4 {
5 Dispose();
6}
7
8 void IDisposable.Dispose()
9 {
10 Dispose(true);
11 GC.SuppressFinalize(this);
12}
13
14 protected virtual void Dispose(bool disposing)
15 {
16 // Das Gleiche wie zuvor
17}
18}
19
Hier kommt eine sogenannte explizite Implementierung der Schnittstelle zum Einsatz: void IDisposable.Dispose(). Auf diese explizite Implementierung kann nur über die Schnittstelle zugegriffen werden, nicht jedoch über die implementierende Klasse. daher:
1 Foo foo = new Foo();
2
3 foo.Dispose(); // Fehler
4 (foo as IDisposable).Dispose(); // Richtig
5
Damit ist für beides gesorgt. Wer Close verwenden möchte, kann foo.Close() direkt verwenden und wird Dispose() nicht sehen. Für diejenigen, die Dispose mögen, kann er den Typ zum Aufrufen in IDisposable konvertieren oder die using-Anweisung verwenden. Große Freude für beide!
http://www.cnblogs.com/xlshcn/archive/2007/01/16/idisposable.html