3 Stimmen

Automatischer Aufruf von base.Dispose() aus abgeleiteten Klassen

Bearbeiten - Neue Frage

Okay, formulieren wir die Frage etwas allgemeiner.

Gibt es eine Möglichkeit, mit Hilfe von Reflection zur Laufzeit dynamisch eine Basisklassenmethode aufzurufen, die Sie möglicherweise überschreiben. Sie können das Schlüsselwort "base" nicht zur Kompilierzeit verwenden, da Sie nicht sicher sein können, dass es existiert. Zur Laufzeit möchte ich meine Vorgängermethoden auflisten und die Vorgängermethoden aufrufen.

Ich habe versucht, mit GetMethods() und so weiter, aber alles, was sie zurückgeben, sind "Zeiger" auf die meisten abgeleiteten Implementierung der Methode. Nicht eine Implementierung auf einer Basisklasse.

Hintergrund

Wir entwickeln ein System in C# 3.0 mit einer relativ großen Klassenhierarchie. Einige dieser Klassen, irgendwo in der Hierarchie, haben Ressourcen, die entsorgt werden müssen, diese implementieren die IDer Einwegartikel Schnittstelle.

Das Problem

Um die Wartung und das Refactoring des Codes zu erleichtern, möchte ich nun eine Möglichkeit finden, für Klassen, die IDisposable, "automatisch" aufzurufen base.Dispose(bDisposing) wenn einer der Vorfahren ebenfalls IDisposable implementiert. Auf diese Weise kann eine Klasse, die in der Hierarchie weiter oben steht, wenn sie implementiert oder aufhört, IDisposable zu implementieren, wird dies automatisch behoben.

Das Problem ist ein zweifaches.

  • Zunächst wird festgestellt, ob ein Vorfahr IDisposable implementiert.
  • Zweitens: Bedingter Aufruf von base.Dispose(bDisposing).

Den ersten Teil, die Suche nach Vorfahren, die IDisposable implementieren, habe ich bewältigen können.

Der zweite Teil ist der heikle Teil. Trotz all meiner Bemühungen ist es mir nicht gelungen, base.Dispose(bDisposing) aus einer abgeleiteten Klasse aufzurufen. Alle meine Versuche schlugen fehl. Sie verursachten entweder Kompilierungsfehler oder riefen die falsche Dispose()-Methode auf, d. h. die am meisten abgeleitete Methode, was zu einer Endlosschleife führte.

Das Hauptproblem ist, dass Sie kann nicht tatsächlich auf base.Dispose() verweisen direkt in Ihren Code einfügen, wenn es kein solches Element wie Vorgänger gibt, der es implementiert ( daran erinnert werden, dass es vielleicht noch keine Vorfahren gibt, die IDisposable implementieren, aber ich möchte, dass der abgeleitete Code bereit ist, wenn und falls solche so etwas in der Zukunft passiert ). Damit bleibt uns die Reflexion Mechanismen, aber ich habe keinen geeigneten Weg gefunden, dies zu tun. Unser Code ist ziemlich voll mit fortgeschrittenen Reflexionstechniken, und ich denke, ich habe dort nichts Offensichtliches übersehen.

Meine Lösung

Mein bester Versuch war es, einige bedingte Code mit in kommentierten Code haben. Eine Änderung der IDisposable-Hierarchie würde entweder den Build unterbrechen (wenn kein IDisposable-Vorfahr existiert) oder eine Ausnahme auslösen (wenn es IDisposable-Vorfahren gibt, aber base.Dispose nicht aufgerufen wird).

Im folgenden Code zeige ich Ihnen, wie meine Methode Dispose(bDisposing) aussieht. Ich füge diesen Code an das Ende aller Dispose() Methoden in der gesamten Hierarchie. Alle neuen Klassen werden aus Vorlagen erstellt, die auch diesen Code enthalten.

public class MyOtherClassBase
{
    // ...
}

public class MyDerivedClass : MyOtherClassBase, ICalibrable
{

    private bool m_bDisposed = false;

    ~MyDerivedClass()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool bDisposing)
    {
        if (!m_bDisposed) {
            if (bDisposing) {
                // Dispose managed resources
            }
            // Dispose unmanaged resources
        }
        m_bDisposed = true;

        Type baseType = typeof(MyDerivedClass).BaseType;
        if (baseType != null) {
            if (baseType.GetInterface("IDisposable") != null) {
                // If you have no ancestors implementing base.Dispose(...), comment
                // the following line AND uncomment the throw. 
                //
                // This way, if any of your ancestors decide one day to implement 
                // IDisposable you will know about it right away and proceed to 
                // uncomment the base.Dispose(...) in addition to commenting the throw.
                //base.Dispose(bDisposing);
                throw new ApplicationException("Ancestor base.Dispose(...) not called - " 
                                               + baseType.ToString());
            }
        }
    }
}

Also, ich frage, gibt es eine Möglichkeit, base.Dispose() automatisch/bedingte stattdessen aufrufen?

Mehr Hintergrund

Es gibt einen weiteren Mechanismus in der Anwendung, bei dem alle Objekte bei einer Hauptklasse registriert werden. Die Klasse prüft, ob sie IDisposable implementieren. Ist dies der Fall, werden sie von der Anwendung ordnungsgemäß entsorgt. Dadurch wird vermieden, dass der Code, der die Klassen verwendet, sich mit Dispose() aufzurufen. Daher funktioniert das Hinzufügen von IDisposable zu einer Klasse, die keine Vorfahren von IDisposable hat, immer noch perfekt.

10voto

Mike Dimmick Punkte 9542

Das Standardmuster besteht darin, dass Ihre Basisklasse IDisposable und die nicht-virtuelle Methode Dispose() implementiert und eine virtuelle Methode Dispose(bool) implementiert, die von den Klassen, die Wegwerfressourcen enthalten, überschrieben werden muss. Sie sollten immer ihre Basismethode Dispose(bool) aufrufen, die sich schließlich bis zur obersten Klasse in der Hierarchie hochkettet. Nur die Klassen, die die Methode überschreiben, werden aufgerufen, so dass die Kette normalerweise recht kurz ist.

Finalizer, in C# ~Class geschrieben: Don't. Nur sehr wenige Klassen benötigen einen, und es ist sehr einfach, versehentlich große Objektgraphen zu behalten, da die Finalizer mindestens zwei Collections erfordern, bevor der Speicher freigegeben wird. Bei der ersten Sammlung, nachdem das Objekt nicht mehr referenziert wird, wird es in eine Warteschlange von Finalizern gestellt, die ausgeführt werden sollen. Diese werden ausgeführt in einem separaten, eigenen Thema die nur Finalizer ausführt (wenn sie blockiert wird, werden keine Finalizer mehr ausgeführt und der Speicherverbrauch explodiert). Sobald der Finalizer gelaufen ist, gibt die nächste Collection, die die entsprechende Generation sammelt, das Objekt und alles andere frei, auf das es verwiesen hat und das nicht anderweitig referenziert ist. Da es die erste Auflistung überlebt, wird es leider in die ältere Generation eingeordnet, die seltener gesammelt wird. Aus diesem Grund sollten Sie Dispose früh und oft einsetzen.

Im Allgemeinen sollten Sie eine kleine Ressourcen-Wrapper-Klasse implementieren, die nur verwaltet die Lebensdauer der Ressource und implementiert einen Finalizer für diese Klasse, sowie IDisposable. Der Benutzer der Klasse sollte dann Dispose für diese Klasse aufrufen, wenn sie entsorgt wird. Es sollte keine Rückkopplung zum Benutzer geben. Auf diese Weise landet nur das, was tatsächlich finalisiert werden muss, in der Finalisierungswarteschlange.

Wenn Sie sie irgendwo in der Hierarchie benötigen, sollte die Basisklasse, die IDisposable implementiert, den Finalizer implementieren und Dispose(bool) aufrufen, wobei false als Parameter übergeben wird.

WARNUNG für Windows Mobile-Entwickler (VS2005 und 2008, .NET Compact Framework 2.0 und 3.5): Viele Nicht-Steuerelemente, die Sie auf Ihrer Designer-Oberfläche ablegen, z. B. Menüleisten, Timer, HardwareButtons, leiten sich von System.ComponentModel.Component ab, das einen Finalizer implementiert. Bei Desktop-Projekten fügt Visual Studio die Komponenten in einen System.ComponentModel.Container namens components und generiert Code, um das Formular zu entsorgen, wenn es entsorgt wird - das wiederum entsorgt alle hinzugefügten Komponenten. Für die mobilen Projekte wird der Dispose-Code components erzeugt wird, aber wenn man eine Komponente auf die Oberfläche fallen lässt, wird kein Code zum Hinzufügen der Komponente zu components . Sie müssen dies selbst in Ihrem Konstruktor nach dem Aufruf von InitializeComponent tun.

3voto

Bryant Punkte 8670

Ich persönlich denke, dass Sie dies besser mit etwas wie FxCop handhaben sollten. Sie sollten in der Lage sein, eine Regel zu schreiben, die prüft, ob beim Erstellen eines Objekts, das IDisposable implementiert, eine using-Anweisung verwendet wird.

Es scheint mir ein wenig schmutzig, ein Objekt automatisch zu entsorgen.

2voto

Scott Dorman Punkte 41206

Es gibt keine "anerkannte" Methode, dies zu tun. Sie sollten Ihre Aufräumlogik (unabhängig davon, ob sie innerhalb eines Dispose oder eines Finalizers abläuft) so einfach wie möglich gestalten, damit sie nicht fehlschlägt. Die Verwendung von Reflection innerhalb eines Dispose (und insbesondere eines Finalizers) ist generell eine schlechte Idee.

Was die Implementierung von Finalizern angeht, so ist das im Allgemeinen nicht nötig. Finalizer fügen Ihrem Objekt Kosten hinzu und sind schwer korrekt zu schreiben, da die meisten der Annahmen, die Sie normalerweise über den Zustand des Objekts und die Laufzeit machen können, nicht gültig sind.

Siehe dies Artikel für weitere Informationen über das Dispose-Muster.

2voto

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestDisposeInheritance
{
    class Program
    {
        static void Main(string[] args)
        {
            classC c = new classC();
            c.Dispose();
        }
    }

    class classA: IDisposable 
    { 
        private bool m_bDisposed;
        protected virtual void Dispose(bool bDisposing)
        {
            if (!m_bDisposed)
            {
                if (bDisposing)
                {
                    // Dispose managed resources
                    Console.WriteLine("Dispose A"); 
                }
                // Dispose unmanaged resources 
            }
        }
        public void Dispose() 
        {
            Dispose(true);
            GC.SuppressFinalize(this);
            Console.WriteLine("Disposing A"); 
        } 
    } 

    class classB : classA, IDisposable 
    {
        private bool m_bDisposed;
        public void Dispose()
        {
            Dispose(true);
            base.Dispose();
            GC.SuppressFinalize(this);
            Console.WriteLine("Disposing B");
        }

        protected override void Dispose(bool bDisposing)
        {
            if (!m_bDisposed)
            {
                if (bDisposing)
                {
                    // Dispose managed resources
                    Console.WriteLine("Dispose B");
                }
                // Dispose unmanaged resources 
            }
        }
    } 

    class classC : classB, IDisposable 
    {
        private bool m_bDisposed;
        public void Dispose() 
        {
            Dispose(true);
            base.Dispose();
            GC.SuppressFinalize(this);
            Console.WriteLine("Disposing C");             
        }
        protected override void Dispose(bool bDisposing)
        {
            if (!m_bDisposed)
            {
                if (bDisposing)
                {
                    // Dispose managed resources
                    Console.WriteLine("Dispose C");             
                }
                // Dispose unmanaged resources 
            }
        }
    } 

}

0voto

Wenn Sie [basetype].Invoke("Dispose"...) verwenden wollten, könnten Sie den Funktionsaufruf implementieren, ohne dass sich der Debugger beschwert. Später dann, wenn der Basistyp tatsächlich die IDisposable-Schnittstelle implementiert, wird der richtige Aufruf ausgeführt.

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