Frage

Ich habe eine C#-Klasse mit einem Dispose Funktion über IDisposable.Es ist für den Innenbereich vorgesehen using blockieren, damit die teure Ressource, die es verwaltet, sofort freigegeben werden kann.

Das Problem besteht darin, dass ein Fehler aufgetreten ist, als zuvor eine Ausnahme ausgelöst wurde Dispose aufgerufen wurde und der Programmierer es versäumte, es zu verwenden using oder finally.

In C++ musste ich mir darüber nie Sorgen machen.Der Aufruf des Destruktors einer Klasse würde automatisch am Ende des Objektbereichs eingefügt.Die einzige Möglichkeit, dies zu vermeiden, besteht darin, den neuen Operator zu verwenden und das Objekt hinter einen Zeiger zu halten. Dies erfordert jedoch zusätzliche Arbeit für den Programmierer, die er nicht aus Versehen tun würde, beispielsweise wenn er die Verwendung vergisst using.

Gibt es eine Möglichkeit für a using Block soll automatisch in C# verwendet werden?

Vielen Dank.

AKTUALISIEREN:

Ich möchte erklären, warum ich die Finalizer-Antworten nicht akzeptiere.Diese Antworten sind an sich technisch korrekt, aber es handelt sich nicht um Destruktoren im C++-Stil.

Hier ist der Fehler, den ich gefunden habe, auf das Wesentliche reduziert ...

try
{
    PleaseDisposeMe a = new PleaseDisposeMe();
    throw new Exception();
    a.Dispose();
}
catch (Exception ex)
{
    Log(ex);
}

// This next call will throw a time-out exception unless the GC
// runs a.Dispose in time.
PleaseDisposeMe b = new PleaseDisposeMe();

Benutzen FXCop ist ein ausgezeichneter Vorschlag, aber wenn das meine einzige Antwort ist, müsste meine Frage zu einem Appell an die C#-Leute werden oder C++ verwenden.Zwanzig verschachtelte using-Anweisungen?

War es hilfreich?

Lösung

Leider gibt es keine Möglichkeit, dies direkt im Code zu tun.Wenn dies ein internes Problem ist, gibt es verschiedene Code-Analyselösungen, die diese Art von Problemen erkennen können.Haben Sie sich FxCop angesehen?Ich denke, dass dies diese Situationen und alle Fälle abdeckt, in denen IDisposable-Objekte hängen bleiben könnten.Wenn es sich um eine Komponente handelt, die von Personen außerhalb Ihrer Organisation verwendet wird, und Sie FxCop nicht benötigen können, ist die Dokumentation wirklich Ihr einziger Ausweg :).

Bearbeiten:Im Fall von Finalisierern garantiert dies nicht wirklich, wann die Finalisierung stattfinden wird.Dies könnte also eine Lösung für Sie sein, aber es hängt von der Situation ab.

Andere Tipps

Wo ich arbeite, wenden wir die folgenden Richtlinien an:

  • Jede IDisposable-Klasse muss Habe einen Finalizer
  • Wenn ein IDisposable-Objekt verwendet wird, muss es innerhalb eines „using“-Blocks verwendet werden.Die einzige Ausnahme besteht darin, dass das Objekt Mitglied einer anderen Klasse ist. In diesem Fall muss die enthaltende Klasse IDisposable sein und die Methode „Dispose“ des Mitglieds in ihrer eigenen Implementierung von „Dispose“ aufrufen.Das bedeutet, dass „Dispose“ vom Entwickler niemals aufgerufen werden sollte, außer innerhalb einer anderen „Dispose“-Methode, wodurch der in der Frage beschriebene Fehler behoben wird.
  • Der Code in jedem Finalizer muss mit einem Warn-/Fehlerprotokoll beginnen, das uns darüber informiert, dass der Finalizer aufgerufen wurde.Auf diese Weise haben Sie eine äußerst gute Chance, die oben beschriebenen Fehler zu erkennen, bevor Sie den Code veröffentlichen, und es kann außerdem ein Hinweis auf Fehler sein, die in Ihrem System auftreten.

Um uns das Leben zu erleichtern, haben wir in unserer Infrastruktur auch eine SafeDispose-Methode, die für alle Fälle die Dispose-Methode ihres Arguments innerhalb eines Try-Catch-Blocks (mit Fehlerprotokollierung) aufruft (obwohl Dispose-Methoden keine Ausnahmen auslösen sollen). ).

Siehe auch: Chris LyonVorschläge zu IDisposable

Bearbeiten:@Zänkisch:Eine Sache, die Sie tun sollten, ist, GC.SuppressFinalize innerhalb von „Dispose“ aufzurufen, damit das Objekt, wenn es entsorgt wurde, nicht „neu entsorgt“ wird.

Außerdem ist es in der Regel ratsam, eine Markierung anzubringen, die anzeigt, ob das Objekt bereits entsorgt wurde oder nicht.Das folgende Muster ist normalerweise ziemlich gut:

class MyDisposable: IDisposable {
    public void Dispose() {
        lock(this) {
            if (disposed) {
                return;
            }

            disposed = true;
        }

        GC.SuppressFinalize(this);

        // Do actual disposing here ...
    }

    private bool disposed = false;
}

Natürlich ist das Sperren nicht immer notwendig, aber wenn Sie nicht sicher sind, ob Ihre Klasse in einer Multithread-Umgebung verwendet werden würde oder nicht, ist es ratsam, es beizubehalten.

@Zänkisch

If wird aufgerufen, wenn das Objekt aus dem Gültigkeitsbereich verschoben und vom Garbage Collector aufgeräumt wird.

Diese Aussage ist irreführend und wie ich sie gelesen habe, falsch:Es gibt absolut keine Garantie, wann der Finalizer aufgerufen wird.Sie haben völlig Recht, dass billpg einen Finalizer implementieren sollte.Es wird jedoch nicht wie gewünscht automatisch aufgerufen, wenn das Objekt den Gültigkeitsbereich verlässt. Beweis, der erste Aufzählungspunkt unten Für Finalisierungsvorgänge gelten die folgenden Einschränkungen.

Tatsächlich hat Microsoft Chris Sells einen Zuschuss gewährt, um eine Implementierung von .NET zu erstellen, die Referenzzählung anstelle von Garbage Collection verwendet Verknüpfung.Wie sich herausstellte, gab es eine beträchtlich Leistungshit.

~ClassName()
{
}

BEARBEITEN (fett):

If wird aufgerufen, wenn das Objekt aus dem Gültigkeitsbereich verschoben und vom Garbage Collector aufgeräumt wird Dies ist jedoch nicht deterministisch und es kann nicht garantiert werden, dass es zu einem bestimmten Zeitpunkt geschieht.Dies wird als Finalizer bezeichnet.Alle Objekte mit einem Finalizer werden vom Garbage Collector in eine spezielle Finalisierungswarteschlange gestellt, wo die Finalisierungsmethode für sie aufgerufen wird (technisch gesehen ist es also ein Leistungseinbruch, leere Finalisierungen zu deklarieren).

Das „akzeptierte“ Entsorgungsmuster gemäß den Rahmenrichtlinien lautet bei nicht verwalteten Ressourcen wie folgt:

    public class DisposableFinalisableClass : IDisposable
    {
        ~DisposableFinalisableClass()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // tidy managed resources
            }

            // tidy unmanaged resources
        }
    }

Das oben Gesagte bedeutet also, dass die nicht verwalteten Ressourcen aufgeräumt werden, wenn jemand Dispose aufruft.Falls jedoch jemand vergisst, Dispose aufzurufen, oder eine Ausnahme verhindert, dass Dispose aufgerufen wird, werden die nicht verwalteten Ressourcen dennoch aufgeräumt, und zwar nur wenig später, wenn der GC in schmutzige Hände gerät (was auch das Schließen oder unerwartete Beenden der Anwendung einschließt). ).

Die beste Vorgehensweise besteht darin, in Ihrer Klasse einen Finalizer zu verwenden und ihn immer zu verwenden using Blöcke.

Es gibt allerdings kein wirkliches direktes Äquivalent, Finalisierer sehen aus wie C-Destruktoren, verhalten sich aber anders.

Du sollst nisten using Blöcke, deshalb werden sie im C#-Codelayout standardmäßig in derselben Zeile platziert ...

using (SqlConnection con = new SqlConnection("DB con str") )
using (SqlCommand com = new SqlCommand( con, "sql query") )
{
    //now code is indented one level
    //technically we're nested twice
}

Wenn Sie es nicht verwenden using Sie können sowieso einfach das tun, was es unter der Haube tut:

PleaseDisposeMe a;
try
{
    a = new PleaseDisposeMe();
    throw new Exception();
}
catch (Exception ex) { Log(ex); }  
finally {    
    //this always executes, even with the exception
    a.Dispose(); 
}

Mit verwaltetem Code ist C# sehr, sehr gut darin, seinen eigenen Speicher zu pflegen, selbst wenn Dinge schlecht entsorgt sind.Wenn Sie häufig mit nicht verwalteten Ressourcen arbeiten, ist es nicht so stark.

Das ist nicht anders, als wenn ein Programmierer die Verwendung vergisst löschen in C++, außer dass der Garbage Collector es zumindest hier irgendwann noch einholen wird.

Und Sie müssen IDisposable nie verwenden, wenn die einzige Ressource, um die Sie sich Sorgen machen, der Speicher ist.Das Framework erledigt das selbstständig.IDisposable gilt nur für nicht verwaltete Ressourcen wie Datenbankverbindungen, Dateistreams, Sockets und dergleichen.

Ein besserer Entwurf besteht darin, diese Klasse dazu zu bringen, die teure Ressource selbst freizugeben, bevor sie entsorgt wird.

Wenn es sich beispielsweise um eine Datenbankverbindung handelt, stellen Sie die Verbindung nur bei Bedarf her und geben Sie sie sofort frei, lange bevor die eigentliche Klasse verworfen wird.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top