C # 2008
Ich arbeite jetzt schon eine Weile daran und bin immer noch verwirrt über die Verwendung von Finalize- und Dispose-Methoden im Code. Meine Fragen sind unten:
Ich weiß, dass wir nur einen Finalizer benötigen, wenn wir nicht verwaltete Ressourcen entsorgen. Wenn jedoch verwaltete Ressourcen vorhanden sind, die nicht verwaltete Ressourcen aufrufen, muss dann noch ein Finalizer implementiert werden?
Wenn ich jedoch eine Klasse entwickle, die keine nicht verwalteten Ressourcen verwendet - direkt oder indirekt, sollte ich die implementieren
IDisposable
, damit die Clients dieser Klasse die Anweisung 'using' verwenden können?Wäre es möglich, IDisposable zu implementieren, damit Clients Ihrer Klasse die using-Anweisung verwenden können?
using(myClass objClass = new myClass()) { // Do stuff here }
Ich habe diesen einfachen Code unten entwickelt, um die Verwendung von Finalize / Dispose zu demonstrieren:
public class NoGateway : IDisposable { private WebClient wc = null; public NoGateway() { wc = new WebClient(); wc.DownloadStringCompleted += wc_DownloadStringCompleted; } // Start the Async call to find if NoGateway is true or false public void NoGatewayStatus() { // Start the Async's download // Do other work here wc.DownloadStringAsync(new Uri(www.xxxx.xxx)); } private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { // Do work here } // Dispose of the NoGateway object public void Dispose() { wc.DownloadStringCompleted -= wc_DownloadStringCompleted; wc.Dispose(); GC.SuppressFinalize(this); } }
Frage zum Quellcode:
Hier habe ich den Finalizer nicht hinzugefügt, und normalerweise wird der Finalizer vom GC aufgerufen, und der Finalizer ruft Dispose auf. Wann rufe ich die Dispose-Methode auf, da ich den Finalizer nicht habe? Ist es der Client der Klasse, der es aufrufen muss?
Meine Klasse im Beispiel heißt also NoGateway, und der Client kann die Klasse folgendermaßen verwenden und entsorgen:
using(NoGateway objNoGateway = new NoGateway()) { // Do stuff here }
Würde die Dispose-Methode automatisch aufgerufen, wenn die Ausführung das Ende des using-Blocks erreicht, oder muss der Client die dispose-Methode manuell aufrufen? dh
NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it
Ich benutze die
WebClient
Klasse in meinerNoGateway
Klasse. DaWebClient
die Geräte -IDisposable
Schnittstelle, bedeutet das , dieWebClient
indirekt von nicht verwalteten Ressourcen verwendet? Gibt es eine feste Regel, um dies zu befolgen? Woher weiß ich, dass eine Klasse nicht verwaltete Ressourcen verwendet?
quelle
Antworten:
Das empfohlene IDisposable-Muster finden Sie hier . Wenn Sie eine Klasse programmieren, die IDisposable verwendet, sollten Sie im Allgemeinen zwei Muster verwenden:
Wenn Sie eine versiegelte Klasse implementieren, die keine nicht verwalteten Ressourcen verwendet, implementieren Sie einfach eine Dispose-Methode wie bei normalen Schnittstellenimplementierungen:
Gehen Sie beim Implementieren einer nicht versiegelten Klasse folgendermaßen vor:
Beachten Sie, dass ich keinen Finalizer in deklariert habe
B
. Sie sollten einen Finalizer nur implementieren, wenn Sie tatsächlich nicht verwaltete Ressourcen zur Verfügung haben. Die CLR behandelt finalisierbare Objekte anders als nicht finalisierbare Objekte, selbst wenn sieSuppressFinalize
aufgerufen wird.Sie sollten also keinen Finalizer deklarieren, es sei denn, Sie müssen, aber Sie geben den Erben Ihrer Klasse einen Haken, um Ihren aufzurufen
Dispose
und einen Finalizer selbst zu implementieren, wenn sie nicht verwaltete Ressourcen direkt verwenden:Wenn Sie nicht verwaltete Ressourcen nicht direkt verwenden (
SafeHandle
und Freunde nicht zählen, da sie ihre eigenen Finalizer deklarieren), implementieren Sie keinen Finalizer, da der GC mit finalisierbaren Klassen anders umgeht, auch wenn Sie den Finalizer später unterdrücken. Beachten Sie auch, dass, obwohlB
es keinen Finalizer gibt, immer noch aufgerufen wirdSuppressFinalize
, alle Unterklassen, die einen Finalizer implementieren, korrekt zu behandeln.Wenn eine Klasse die IDisposable-Schnittstelle implementiert, bedeutet dies, dass irgendwo nicht verwaltete Ressourcen vorhanden sind, die entfernt werden sollten, wenn Sie die Klasse nicht mehr verwenden. Die tatsächlichen Ressourcen sind in den Klassen gekapselt. Sie müssen sie nicht explizit löschen. Durch einfaches Aufrufen
Dispose()
oder Umschließen der Klasse in ausing(...) {}
wird sichergestellt, dass nicht verwaltete Ressourcen bei Bedarf entfernt werden.quelle
IDisposable
, besteht die Möglichkeit, dass es ohnehin eine Weile herumhängt. Sie sparen der CLR den Aufwand, sie von Gen0 -> Gen1 -> Gen2Das offizielle Muster
IDisposable
ist schwer zu verstehen. Ich glaube, das ist besser :Eine noch bessere Lösung besteht darin, eine Regel zu haben, nach der Sie immer eine Wrapper-Klasse für jede nicht verwaltete Ressource erstellen müssen, die Sie verarbeiten müssen:
Mit
SafeHandle
und seinen Derivaten sollten diese Klassen sehr selten sein .Das Ergebnis für verfügbare Klassen, die sich selbst bei Vorhandensein von Vererbung nicht direkt mit nicht verwalteten Ressourcen befassen, ist leistungsstark: Sie müssen sich nicht mehr mit nicht verwalteten Ressourcen befassen . Sie sind einfach zu implementieren und zu verstehen:
quelle
disposed
Flagge hinzufügen und entsprechend prüfen.disposed
Flag hinzuzufügen , es vor dem Entsorgen zu überprüfen und nach dem Entsorgen zu setzen. Suchen Sie hier nach der Idee. Sie sollten das Flag auch vor allen Methoden der Klasse überprüfen. Macht Sinn? Ist es kompliziert?Beachten Sie, dass jede IDisposable-Implementierung dem folgenden Muster (IMHO) folgen sollte. Ich habe dieses Muster basierend auf Informationen von mehreren ausgezeichneten .NET "Göttern" der .NET Framework Design Guidelines entwickelt (beachten Sie, dass MSDN dies aus irgendeinem Grund nicht befolgt!). Die .NET Framework Design Guidelines wurden von Krzysztof Cwalina (damals CLR-Architekt) und Brad Abrams (ich glaube der damalige CLR-Programmmanager) sowie Bill Wagner ([Effective C #] und [More Effective C #]) geschrieben Suchen Sie nach diesen auf Amazon.com:
Beachten Sie, dass Sie NIEMALS einen Finalizer implementieren sollten, es sei denn, Ihre Klasse enthält direkt nicht verwaltete Ressourcen (erbt diese nicht). Sobald Sie einen Finalizer in einer Klasse implementieren, wird er garantiert für eine zusätzliche Sammlung leben, auch wenn er nie aufgerufen wird. Es wird automatisch in die Finalisierungswarteschlange gestellt (die auf einem einzelnen Thread ausgeführt wird). Ein sehr wichtiger Hinweis: Der gesamte Code, der in einem Finalizer ausgeführt wird (falls Sie einen implementieren müssen), MUSS threadsicher UND ausnahmesicher sein! SCHLECHTE Dinge passieren sonst ... (dh unbestimmtes Verhalten und im Falle einer Ausnahme ein schwerwiegender nicht behebbarer Anwendungsabsturz).
Das Muster, das ich zusammengestellt (und für das ich ein Code-Snippet geschrieben habe), lautet wie folgt:
Hier ist der Code zum Implementieren von IDisposable in einer abgeleiteten Klasse. Beachten Sie, dass Sie die Vererbung von IDisposable in der Definition der abgeleiteten Klasse nicht explizit auflisten müssen.
Ich habe diese Implementierung in meinem Blog veröffentlicht unter: So implementieren Sie das Entsorgungsmuster ordnungsgemäß
quelle
Interlocked.Exchange
Ganzzahl-IsDisposed
Flags in der nicht-virtuellen Wrapper-Funktion sicherer wäre.Ich stimme pm100 zu (und hätte dies in meinem früheren Beitrag ausdrücklich sagen sollen).
Sie sollten IDisposable niemals in einer Klasse implementieren, es sei denn, Sie benötigen es. Um genau zu sein, gibt es ungefähr fünf Mal, wenn Sie IDisposable jemals benötigen / implementieren würden:
Ihre Klasse enthält explizit (dh nicht über Vererbung) verwaltete Ressourcen, die IDisposable implementieren, und sollte bereinigt werden, sobald Ihre Klasse nicht mehr verwendet wird. Wenn Ihre Klasse beispielsweise eine Instanz eines Streams, eines DbCommand, einer DataTable usw. enthält.
Ihre Klasse enthält explizit alle verwalteten Ressourcen, die eine Close () -Methode implementieren - z. B. IDataReader, IDbConnection usw. Beachten Sie, dass einige dieser Klassen IDisposable implementieren, indem sie sowohl über Dispose () als auch über eine Close () -Methode verfügen.
Ihre Klasse enthält explizit eine nicht verwaltete Ressource - z. B. ein COM-Objekt, Zeiger (ja, Sie können Zeiger in verwaltetem C # verwenden, diese müssen jedoch in "unsicheren" Blöcken usw. deklariert werden. Bei nicht verwalteten Ressourcen sollten Sie dies ebenfalls sicherstellen Rufen Sie System.Runtime.InteropServices.Marshal.ReleaseComObject () auf dem RCW auf. Obwohl das RCW theoretisch ein verwalteter Wrapper ist, wird unter den Deckblättern immer noch Referenzzählungen durchgeführt.
Wenn Ihre Klasse Ereignisse mit starken Referenzen abonniert. Sie müssen sich von den Ereignissen abmelden. Stellen Sie immer sicher, dass diese nicht null sind, bevor Sie versuchen, die Registrierung aufzuheben / zu trennen!.
Ihre Klasse enthält eine beliebige Kombination der oben genannten ...
Eine empfohlene Alternative zur Arbeit mit COM-Objekten und zur Verwendung von Marshal.ReleaseComObject () ist die Verwendung der Klasse System.Runtime.InteropServices.SafeHandle.
Das BCL (Base Class Library Team) hat hier einen guten Blog-Beitrag darüber http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx
Ein sehr wichtiger Hinweis ist, dass Sie, wenn Sie mit WCF arbeiten und Ressourcen bereinigen, den Block "using" IMMER vermeiden sollten. Es gibt viele Blog-Beiträge und einige auf MSDN darüber, warum dies eine schlechte Idee ist. Ich habe auch hier darüber gepostet - Verwenden Sie 'using ()' nicht mit einem WCF-Proxy
quelle
Verwenden von Lambdas anstelle von IDisposable.
Ich war noch nie begeistert von der ganzen Idee mit / IDisposable. Das Problem ist, dass der Anrufer Folgendes tun muss:
Meine neue bevorzugte Methode ist die Verwendung einer Factory-Methode und eines Lambda
Stellen Sie sich vor, ich möchte etwas mit einer SqlConnection machen (etwas, das in eine using-Datei eingeschlossen werden sollte). Klassisch würden Sie tun
Neuer Weg
Im ersten Fall konnte der Aufrufer die using-Syntax einfach nicht verwenden. Im zweiten Fall hat der Benutzer keine Wahl. Es gibt keine Methode, die ein SqlConnection-Objekt erstellt. Der Aufrufer muss DoWithConnection aufrufen.
DoWithConnection sieht so aus
MakeConnection
ist jetzt privatquelle
DoForAll(Action<T>) where T:IComparable<T>
, die den angegebenen Delegaten für jeden Datensatz aufruft. Wie würde bei zwei solchen Objekten, die beide Daten in sortierter Reihenfolge zurückgeben, eines alle Elemente ausgeben, die in einer Sammlung vorhanden sind, das andere jedoch nicht? Wenn die Typen implementiert sindIEnumerable<T>
, könnte man einen Zusammenführungsvorgang ausführen, aber das funktioniert nicht mitDoForAll
.DoForAll
Sammlungen zusammenzuführen, ohne zuerst eine in ihrer Gesamtheit in eine andere Struktur kopieren zu müssen, besteht darin, zwei Threads zu verwenden, die eher ressourcenschonend wären, als nur ein paar IEnumerables zu verwenden und vorsichtig zu sein um sie freizulassen.Niemand beantwortete die Frage, ob Sie IDisposable implementieren sollten, obwohl Sie es nicht benötigen.
Kurze Antwort: Nein
Lange Antwort:
Dies würde es einem Verbraucher Ihrer Klasse ermöglichen, "using" zu verwenden. Die Frage, die ich stellen würde, ist - warum sollten sie es tun? Die meisten Entwickler verwenden "using" nur, wenn sie wissen, dass sie es müssen - und woher wissen sie das? Entweder
Wenn Sie IDisposable implementieren, teilen Sie den Entwicklern (zumindest einigen) mit, dass diese Klasse etwas abschließt, das freigegeben werden muss. Sie werden 'using' verwenden - aber es gibt andere Fälle, in denen using nicht möglich ist (der Umfang des Objekts ist nicht lokal). und in diesen anderen Fällen müssen sie sich Sorgen um die Lebensdauer der Objekte machen - ich würde mir sicher Sorgen machen. Dies ist jedoch nicht erforderlich
Sie implementieren Idisposable, damit sie using verwenden können, aber sie verwenden using nicht, es sei denn, Sie weisen sie an.
Also tu es nicht
quelle
Wenn Sie andere verwaltete Objekte verwenden, die nicht verwaltete Ressourcen verwenden, liegt es nicht in Ihrer Verantwortung, sicherzustellen, dass diese abgeschlossen sind. Sie sind dafür verantwortlich, Dispose für diese Objekte aufzurufen, wenn Dispose für Ihr Objekt aufgerufen wird und dort anhält.
Wenn Ihre Klasse keine knappen Ressourcen verwendet, kann ich nicht verstehen, warum Sie Ihre Klasse dazu bringen würden, IDisposable zu implementieren. Sie sollten dies nur tun, wenn Sie:
Ja, der Code, der Ihren Code verwendet, muss die Dispose-Methode Ihres Objekts aufrufen. Und ja, der Code, der Ihr Objekt verwendet, kann
using
wie gezeigt verwendet werden.(Wieder 2?) Es ist wahrscheinlich, dass der WebClient entweder nicht verwaltete Ressourcen oder andere verwaltete Ressourcen verwendet, die IDisposable implementieren. Der genaue Grund ist jedoch nicht wichtig. Wichtig ist, dass IDisposable implementiert wird. Daher müssen Sie auf dieses Wissen reagieren, indem Sie das Objekt entsorgen, wenn Sie damit fertig sind, auch wenn sich herausstellt, dass WebClient überhaupt keine anderen Ressourcen verwendet.
quelle
Entsorgungsmuster:
Beispiel für die Vererbung:
quelle
Einige Aspekte einer anderen Antwort sind aus zwei Gründen leicht falsch:
Zuerst,
ist eigentlich gleichbedeutend mit:
Dies mag lächerlich klingen, da der 'neue' Operator niemals 'null' zurückgeben sollte, es sei denn, Sie haben eine OutOfMemory-Ausnahme. Beachten Sie jedoch die folgenden Fälle: 1. Sie rufen eine FactoryClass auf, die eine IDisposable-Ressource zurückgibt, oder 2. Wenn Sie einen Typ haben, der je nach Implementierung möglicherweise von IDisposable erbt oder nicht, denken Sie daran, dass das IDisposable-Muster häufig falsch implementiert wurde Zeiten bei vielen Clients, bei denen Entwickler einfach eine Dispose () -Methode hinzufügen, ohne von IDisposable zu erben (schlecht, schlecht, schlecht). Es kann auch vorkommen, dass eine IDisposable-Ressource von einer Eigenschaft oder Methode zurückgegeben wird (wieder schlecht, schlecht, schlecht - geben Sie Ihre IDisposable-Ressourcen nicht weiter).
Wenn der Operator 'as' null zurückgibt (oder eine Eigenschaft oder Methode, die die Ressource zurückgibt) und Ihr Code im Block 'using' vor 'null' schützt, wird Ihr Code beim Versuch, Dispose für ein Null-Objekt aufzurufen, nicht in die Luft gesprengt die 'eingebaute' Nullprüfung.
Der zweite Grund, warum Ihre Antwort nicht korrekt ist, ist der folgende:
Erstens ist die Finalisierung (sowie die GC selbst) nicht deterministisch. Die CLR bestimmt, wann ein Finalizer aufgerufen wird. dh der Entwickler / Code hat keine Ahnung. Wenn das IDisposable-Muster korrekt implementiert ist (wie oben beschrieben) und GC.SuppressFinalize () aufgerufen wurde, wird der Finalizer NICHT aufgerufen. Dies ist einer der Hauptgründe für die korrekte Implementierung des Musters. Da es unabhängig von der Anzahl der logischen Prozessoren nur 1 Finalizer-Thread pro verwaltetem Prozess gibt, können Sie die Leistung leicht beeinträchtigen, indem Sie den Finalizer-Thread sichern oder sogar aufhängen, indem Sie vergessen, GC.SuppressFinalize () aufzurufen.
Ich habe eine korrekte Implementierung des Entsorgungsmusters in meinem Blog veröffentlicht: So implementieren Sie das Entsorgungsmuster ordnungsgemäß
quelle
NoGateway = new NoGateway();
undNoGateway != null
?1) WebClient ist ein verwalteter Typ, sodass Sie keinen Finalizer benötigen. Der Finalizer wird für den Fall benötigt, dass Ihre Benutzer Ihre NoGateway-Klasse nicht entsorgen () und der native Typ (der nicht vom GC erfasst wird) anschließend bereinigt werden muss. In diesem Fall wird der enthaltene WebClient direkt nach dem NoGateway vom GC entsorgt, wenn der Benutzer Dispose () nicht aufruft.
2) Indirekt ja, aber Sie sollten sich keine Sorgen machen müssen. Ihr Code ist in der jetzigen Form korrekt und Sie können Ihre Benutzer nicht daran hindern, Dispose () ganz einfach zu vergessen.
quelle
Muster von msdn
quelle
ist äquivalent zu
Ein Finalizer wird aufgerufen, wenn der GC Ihr Objekt zerstört. Dies kann zu einem völlig anderen Zeitpunkt sein als beim Verlassen Ihrer Methode. Die Option "IDisposable entsorgen" wird sofort aufgerufen, nachdem Sie den using-Block verlassen haben. Daher wird das Muster normalerweise verwendet, um Ressourcen sofort freizugeben, nachdem Sie sie nicht mehr benötigen.
quelle
Soweit ich weiß, wird dringend empfohlen, den Finalizer / Destructor NICHT zu verwenden:
Meistens liegt dies daran, dass man nicht weiß, wann oder WENN es aufgerufen wird. Die Entsorgungsmethode ist viel besser, insbesondere wenn Sie uns direkt verwenden oder entsorgen.
Verwenden ist gut. benutze es :)
quelle