Beginnen Sie mit diesen einfachen Klassen ...
Angenommen, ich habe eine einfache Reihe solcher Klassen:
class Bus
{
Driver busDriver = new Driver();
}
class Driver
{
Shoe[] shoes = { new Shoe(), new Shoe() };
}
class Shoe
{
Shoelace lace = new Shoelace();
}
class Shoelace
{
bool tied = false;
}
A Bus
hat a Driver
, das Driver
hat zwei Shoe
s, jeder Shoe
hat a Shoelace
. Alles sehr dumm.
Fügen Sie Shoelace ein IDisposable-Objekt hinzu
Später entscheide ich, dass eine Operation auf dem Shoelace
Multithreading ausgeführt werden kann, also füge ich eine hinzu, EventWaitHandle
mit der die Threads kommunizieren können. So Shoelace
sieht es jetzt aus:
class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
Implementieren Sie IDisposable auf Shoelace
Jetzt beschwert sich Microsoft FxCop jedoch: "Implementieren Sie IDisposable auf 'Shoelace', da Mitglieder der folgenden IDisposable-Typen erstellt werden: 'EventWaitHandle'."
Okay, ich implementiere IDisposable
weiter Shoelace
und meine nette kleine Klasse wird zu diesem schrecklichen Durcheinander:
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Shoelace()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
}
// No unmanaged resources to release otherwise they'd go here.
}
disposed = true;
}
}
Oder (wie von Kommentatoren hervorgehoben), da es Shoelace
selbst keine nicht verwalteten Ressourcen gibt, könnte ich die einfachere Entsorgungsimplementierung verwenden, ohne den Dispose(bool)
und Destructor zu benötigen :
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
public void Dispose()
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
GC.SuppressFinalize(this);
}
}
Beobachten Sie entsetzt, wie sich IDisposable ausbreitet
Richtig, das ist behoben. Aber jetzt wird sich FxCop beschweren, dass ein Shoe
schafft Shoelace
, so Shoe
muss es IDisposable
auch sein.
Und Driver
schafft muss Shoe
so Driver
sein IDisposable
. Und Bus
schafft Driver
so Bus
muss sein IDisposable
und so weiter.
Plötzlich verursacht meine kleine Änderung an Shoelace
viel Arbeit und mein Chef fragt sich, warum ich zur Kasse gehen muss Bus
, um eine Änderung vorzunehmen Shoelace
.
Die Frage
Wie verhindern Sie diese Ausbreitung IDisposable
und stellen dennoch sicher, dass Ihre nicht verwalteten Objekte ordnungsgemäß entsorgt werden?
quelle
Antworten:
Sie können die Ausbreitung von IDisposable nicht wirklich "verhindern". Einige Klassen müssen entsorgt werden
AutoResetEvent
, und der effizienteste Weg besteht darin, dies in derDispose()
Methode zu tun , um den Overhead von Finalisierern zu vermeiden. Aber diese Methode muss irgendwie aufgerufen werden, so genau wie in Ihrem Beispiel die Klassen, die IDisposable kapseln oder enthalten, diese entsorgen müssen, also müssen sie auch verfügbar sein usw. Der einzige Weg, dies zu vermeiden, ist:using
Muster).In einigen Fällen kann IDisposable ignoriert werden, da es einen optionalen Fall unterstützt. Beispielsweise implementiert WaitHandle IDisposable, um einen benannten Mutex zu unterstützen. Wenn kein Name verwendet wird, führt die Dispose-Methode nichts aus. MemoryStream ist ein weiteres Beispiel, es verwendet keine Systemressourcen und seine Dispose-Implementierung führt auch nichts aus. Sorgfältiges Überlegen, ob eine nicht verwaltete Ressource verwendet wird oder nicht, kann lehrreich sein. So können Sie die verfügbaren Quellen für die .net-Bibliotheken untersuchen oder einen Dekompiler verwenden.
quelle
In Bezug auf die Korrektheit können Sie die Verbreitung von IDisposable durch eine Objektbeziehung nicht verhindern, wenn ein übergeordnetes Objekt ein untergeordnetes Objekt erstellt und im Wesentlichen besitzt, das jetzt verfügbar sein muss. FxCop ist in dieser Situation korrekt und das übergeordnete Element muss IDisposable sein.
Sie können vermeiden, einer Blattklasse in Ihrer Objekthierarchie ein IDisposable hinzuzufügen. Dies ist nicht immer eine leichte Aufgabe, aber eine interessante Übung. Aus logischer Sicht gibt es keinen Grund, warum eine Schuhspitze wegwerfbar sein muss. Anstatt hier ein WaitHandle hinzuzufügen, ist es auch möglich, an der Stelle, an der es verwendet wird, eine Zuordnung zwischen einem ShoeLace und einem WaitHandle hinzuzufügen. Der einfachste Weg führt über eine Dictionary-Instanz.
Wenn Sie den WaitHandle an dem Punkt, an dem der WaitHandle tatsächlich verwendet wird, über eine Karte in eine lose Zuordnung verschieben können, können Sie diese Kette unterbrechen.
quelle
Um eine
IDisposable
Ausbreitung zu verhindern , sollten Sie versuchen, die Verwendung eines Einwegobjekts in einer einzigen Methode zusammenzufassen. Versuchen SieShoelace
anders zu gestalten :Der Umfang des Wartehandles ist auf die
Tie
Methode beschränkt, und die Klasse muss kein verfügbares Feld haben und muss daher nicht selbst verfügbar sein.Da das Wait-Handle ein Implementierungsdetail innerhalb von ist
Shoelace
, sollte es seine öffentliche Schnittstelle in keiner Weise ändern, wie das Hinzufügen einer neuen Schnittstelle in seiner Deklaration. Was passiert dann, wenn Sie kein Einwegfeld mehr benötigenIDisposable
? Entfernen Sie die Deklaration? Wenn Sie über dieShoelace
Abstraktion nachdenken , erkennen Sie schnell, dass sie nicht durch Infrastrukturabhängigkeiten wie verschmutzt werden sollteIDisposable
.IDisposable
sollte für Klassen reserviert werden, deren Abstraktion eine Ressource enthält, die eine deterministische Bereinigung erfordert; dh für Klassen, in denen die Verfügbarkeit Teil der Abstraktion ist .quelle
AutoResetEvent
verwendet, um zwischen verschiedenen Threads zu kommunizieren, die innerhalb derselben Klasse arbeiten. Daher muss es eine Mitgliedsvariable sein. Sie können den Umfang nicht auf eine Methode beschränken. (Stellen Sie sich z. B. vor, ein Thread wartet nur auf etwas Arbeit, indem er blockiertwaitHandle.WaitOne()
. Der Haupt-Thread ruft dann dieshoelace.Tie()
Methode auf, die nur a ausführtwaitHandle.Set()
und sofort zurückkehrt.)Dies ist im Grunde das, was passiert, wenn Sie Komposition oder Aggregation mit Einwegklassen mischen. Wie bereits erwähnt, besteht der erste Ausweg darin, den waitHandle aus dem Schnürsenkel herauszuarbeiten.
Allerdings können Sie das Einwegmuster erheblich reduzieren, wenn Sie nicht über nicht verwaltete Ressourcen verfügen. (Ich suche noch nach einer offiziellen Referenz dafür.)
Sie können jedoch den Destruktor und GC.SuppressFinalize (this) weglassen. und vielleicht die virtuelle Leere aufräumen (bool disposing) ein bisschen.
quelle
Interessanterweise, wenn
Driver
wie oben definiert ist:Wenn
Shoe
dann gemacht wirdIDisposable
, beschwert sich FxCop (v1.36) nicht, dass diesDriver
auch sein sollteIDisposable
.Wenn es jedoch so definiert ist:
dann wird es sich beschweren.
Ich vermute, dass dies nur eine Einschränkung von FxCop ist und keine Lösung, da in der ersten Version die
Shoe
Instanzen noch von der erstellt werdenDriver
und noch irgendwie entsorgt werden müssen.quelle
Shoe
Konstruktoren ausgelöstshoes
würde, würde dies in einem schwierigen Zustand bleiben?Ich glaube nicht, dass es eine technische Möglichkeit gibt, die Verbreitung von IDisposable zu verhindern, wenn Sie Ihr Design so eng miteinander verbinden. Man sollte sich dann fragen, ob das Design stimmt.
In Ihrem Beispiel halte ich es für sinnvoll, dass der Schuh den Schnürsenkel besitzt, und vielleicht sollte der Fahrer seine Schuhe besitzen. Der Bus sollte jedoch nicht den Fahrer besitzen. Normalerweise folgen Busfahrer Bussen nicht zum Schrottplatz :) Bei Fahrern und Schuhen stellen Fahrer selten ihre eigenen Schuhe her, was bedeutet, dass sie diese nicht wirklich "besitzen".
Ein alternatives Design könnte sein:
Das neue Design ist leider komplizierter, da zusätzliche Klassen erforderlich sind, um konkrete Instanzen von Schuhen und Fahrern zu instanziieren und zu entsorgen. Diese Komplikation ist jedoch mit dem zu lösenden Problem verbunden. Das Gute ist, dass Busse nicht mehr nur zum Entsorgen von Schnürsenkeln wegwerfbar sein müssen.
quelle
Wie wäre es mit Inversion of Control?
quelle