2 Stimmen

Ist dies eine legitime Alternative zum "traditionellen" Dispose-Muster für Klassenhierarchien?

Ich bin kein Fan von Standardcode: Die Wiederverwendung durch Kopieren und Einfügen ist potenziell fehleranfällig. Selbst wenn Sie Codeschnipsel oder intelligente Vorlagen verwenden, gibt es keine Garantie dafür, dass der andere Entwickler dies auch getan hat, was bedeutet, dass es keine Garantie dafür gibt, dass er es richtig gemacht hat. Und wenn Sie den Code sehen müssen, müssen Sie ihn auch verstehen und/oder pflegen.

Was ich von der Gemeinschaft wissen möchte, ist: Ist meine Implementierung von IDispose für eine Klassenhierarchie eine legitime Alternative zur "traditionelles" Entsorgungsmuster ? Mit legitim meine ich korrekt, einigermaßen leistungsfähig, robust und wartbar.

Ich habe kein Problem damit, dass diese Alternative schlichtweg falsch ist, aber wenn sie es ist, würde ich gerne wissen, warum.

Bei dieser Implementierung wird davon ausgegangen, dass Sie die volle Kontrolle über die Klassenhierarchie haben; falls nicht, müssen Sie wahrscheinlich auf Boilerplate-Code zurückgreifen. Die Aufrufe von Hinzufügen*() wird normalerweise im Konstruktor vorgenommen.

public abstract class DisposableObject : IDisposable
{
  protected DisposableObject()
  {}

  protected DisposableObject(Action managedDisposer)
  {
     AddDisposers(managedDisposer, null);
  }

  protected DisposableObject(Action managedDisposer, Action unmanagedDisposer)
  {
     AddDisposers(managedDisposer, unmanagedDisposer);
  }

  public bool IsDisposed
  {
     get { return disposeIndex == -1; }
  }

  public void CheckDisposed()
  {
     if (IsDisposed)
        throw new ObjectDisposedException("This instance is disposed.");
  }

  protected void AddDisposers(Action managedDisposer, Action unmanagedDisposer)
  {
     managedDisposers.Add(managedDisposer);
     unmanagedDisposers.Add(unmanagedDisposer);
     disposeIndex++;
  }

  protected void AddManagedDisposer(Action managedDisposer)
  {
     AddDisposers(managedDisposer, null);
  }

  protected void AddUnmanagedDisposer(Action unmanagedDisposer)
  {
     AddDisposers(null, unmanagedDisposer);
  }

  public void Dispose()
  {
     if (disposeIndex != -1)
     {
        Dispose(true);
        GC.SuppressFinalize(this);
     }
  }

  ~DisposableObject()
  {
     if (disposeIndex != -1)
        Dispose(false);
  }

  private void Dispose(bool disposing)
  {
     for (; disposeIndex != -1; --disposeIndex)
     {
        if (disposing)
           if (managedDisposers[disposeIndex] != null)
              managedDisposers[disposeIndex]();
        if (unmanagedDisposers[disposeIndex] != null)
           unmanagedDisposers[disposeIndex]();
     }
  }

  private readonly IList<Action> managedDisposers = new List<Action>();
  private readonly IList<Action> unmanagedDisposers = new List<Action>();
  private int disposeIndex = -1;
}

Dies ist eine "vollständige" Implementierung in dem Sinne, dass ich die Finalisierung unterstütze (da ich weiß, dass die meisten Implementierungen keinen Finalizer benötigen), prüfe, ob ein Objekt entsorgt wurde, usw. Eine echte Implementierung könnte zum Beispiel den Finalizer entfernen oder eine Unterklasse von Einweg-Objekt die den Finalizer enthält. Im Grunde habe ich alles hineingeworfen, was mir zu dieser Frage einfiel.

Wahrscheinlich gibt es einige Sonderfälle und esoterische Situationen, die ich übersehen habe. Ich lade daher jeden ein, diesen Ansatz zu hinterfragen oder ihn zu korrigieren.

Andere Alternativen wären die Verwendung eines einzigen Queue<Disposer> Entsorger en Einweg-Objekt anstelle von zwei Listen; in diesem Fall werden die Entsorger, sobald sie aufgerufen werden, aus der Liste entfernt. Es gibt noch andere leichte Variationen, die ich mir vorstellen kann, aber sie haben das gleiche allgemeine Ergebnis: kein Kesselsteincode.

1 Stimmen

Dies leidet immer noch unter der Tatsache, dass Implementierer daran denken müssen, die Add()-Methode für aggregierte Typen aufzurufen. Ich persönlich glaube nicht, dass dies irgendeinen Vorteil gegenüber dem üblichen Muster hat.

5 Stimmen

Tun Sie es nicht. Verwenden Sie das Dispose-Muster wie geschrieben. Erfahrene Entwickler verstehen den Code auf einen Blick, Sie decken alle Grundlagen ab, Tools wie FxCop helfen bei der Validierung, Sie können Snippets schreiben, um ihn genau zu implementieren, und Sie halten sich an die Microsoft-Standards. bluebytesoftware.com/blog/

0 Stimmen

Nebenbei bemerkt, wenn Sie eine DisposableCollection<TDisposable> wollen, sollten Sie in der Lage sein, eine Reihe von Implementierungen davon zu finden.

5voto

Scott Dorman Punkte 41206

Das erste Problem, auf das Sie möglicherweise stoßen werden, ist, dass C# es nur erlaubt, von einer einzigen Basisklasse zu erben, die in diesem Fall immer sein DisposableObject . Genau hier haben Sie Ihre Klassenhierarchie unübersichtlich gemacht, indem Sie zusätzliche Ebenen erzwungen haben, so dass Klassen, die von DisposableObject und ein anderes Objekt kann dies tun.

Außerdem verursachen Sie mit dieser Implementierung eine Menge Overhead und Wartungsprobleme (ganz zu schweigen von den wiederholten Schulungskosten, die jedes Mal anfallen, wenn jemand Neues zum Projekt stößt und Sie ihm erklären müssen, wie er diese Implementierung anstelle des definierten Musters verwenden soll). Sie wissen, dass Sie mit Ihren beiden Listen mehrere Zustände im Auge behalten müssen, es gibt keine Fehlerbehandlung bei den Aufrufen der Aktionen, die Syntax beim Aufruf einer Aktion sieht "seltsam" aus (es mag zwar üblich sein, eine Methode von einem Array aus aufzurufen, aber die Syntax, einfach das () hinter den Array-Zugriff zu setzen, sieht einfach seltsam aus).

Ich verstehe den Wunsch, die Menge an Boilerplate, die Sie schreiben müssen, zu reduzieren, aber die Verfügbarkeit ist im Allgemeinen nicht einer der Bereiche, in denen ich empfehlen würde, Abkürzungen zu nehmen oder anderweitig vom Muster abzuweichen. Am nächsten komme ich normalerweise, wenn ich eine Hilfsmethode (oder eine Erweiterungsmethode) verwende, die den eigentlichen Aufruf von Dispose() auf ein bestimmtes Objekt. Diese Aufrufe sehen typischerweise so aus:

if (someObject != null)
{
   someObject.Dispose();
}

Dies kann mit einer Hilfsmethode vereinfacht werden, aber bedenken Sie, dass FxCop (oder jedes andere statische Analysewerkzeug, das auf korrekte Dispose-Implementierungen prüft) sich beschweren wird.

Was die Leistung angeht, sollten Sie bedenken, dass Sie bei dieser Art der Implementierung viele Delegatenaufrufe tätigen. Dies ist aufgrund der Natur eines Delegaten etwas kostspieliger als ein normaler Methodenaufruf.

Wartungsfreundlichkeit ist hier definitiv ein Thema. Wie ich bereits erwähnt habe, entstehen jedes Mal, wenn jemand Neues zum Projekt stößt und Sie ihm erklären müssen, wie er diese Implementierung anstelle des definierten Musters verwenden soll, wiederholte Schulungskosten. Und nicht nur das: Sie haben Probleme damit, dass jeder daran denkt, seine Wegwerfobjekte zu Ihren Listen hinzuzufügen.

Insgesamt halte ich dies für eine schlechte Idee, die im weiteren Verlauf viele Probleme verursachen wird, insbesondere wenn das Projekt und das Team größer werden.

0 Stimmen

Gute Argumente, vor allem zur Frage der Ausbildung. Wie ich schon sagte, habe ich kein Problem damit, dass es falsch ist. Ich bin gespannt, was sonst noch alles auftaucht.

2voto

csharptest.net Punkte 58070

Gelegentlich muss ich mehrere gleichzeitig geöffnete Dateien oder andere Ressourcen verfolgen. Wenn ich das tue, verwende ich eine Dienstprogrammklasse ähnlich der folgenden. Dann implementiert das Objekt immer noch Displose(), wie Sie vorschlagen, und selbst die Verfolgung mehrerer Listen (verwaltet/unverwaltet) ist einfach und für Entwickler offensichtlich. Außerdem ist die Ableitung vom List-Objekt kein Zufall, sie erlaubt es, bei Bedarf Remove(obj) aufzurufen. Mein Konstruktor sieht normalerweise so aus:

        _resources = new DisposableList<IDisposable>();
        _file = _resources.BeginUsing(File.Open(...));

Und hier ist die Klasse:

    class DisposableList<T> : List<T>, IDisposable
        where T : IDisposable
    {
        public bool IsDisposed = false;
        public T BeginUsing(T item) { base.Add(item); return item; }
        public void Dispose()
        {
            for (int ix = this.Count - 1; ix >= 0; ix--)
            {
                try { this[ix].Dispose(); }
                catch (Exception e) { Logger.LogError(e); }
                this.RemoveAt(ix);
            }
            IsDisposed = true;
        }
    }

0voto

supercat Punkte 72939

Mir gefällt das allgemeine Muster aus der Antwort von csharptest. Eine Basisklasse zu haben, die um die Entsorgung herum entworfen wurde, ist ein bisschen einschränkend, aber wenn Sie vb. net verwenden oder sich nicht an einigen Spielen mit thread-statischen Variablen stören, wird eine speziell entworfene Basisklasse es möglich machen, Variablen für die Entsorgung zu registrieren, selbst wenn sie in Feldinitialisierern oder Konstruktoren abgeleiteter Klassen erstellt werden (normalerweise gibt es keine Möglichkeit, bereits zugewiesene IDisposable-Felder zu entsorgen, wenn eine Ausnahme in einem Feldinitialisierer ausgelöst wird, und wenn der Konstruktor einer abgeleiteten Klasse eine Ausnahme auslöst, gibt es keine Möglichkeit für das teilweise erstellte Basisobjekt, sich selbst zu entsorgen).

Ich würde mich allerdings nicht mit der Liste der nicht verwalteten Ressourcen befassen. Klassen mit Finalizern sollten keine Referenzen auf Objekte enthalten, die nicht für die Finalisierung benötigt werden. Stattdessen sollten die Dinge, die für die Finalisierung benötigt werden, in einer eigenen Klasse untergebracht werden, und die "Haupt"-Klasse sollte eine Instanz dieser letzteren Klasse erstellen und einen Verweis auf sie behalten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X