416 Stimmen

Verwendung der Finalize/Dispose-Methode in C#

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:

  1. Ich weiß, dass wir nur einen Finalizer benötigen, wenn wir nicht verwaltete Ressourcen entsorgen. Wenn es jedoch verwaltete Ressourcen gibt, die Aufrufe an nicht verwaltete Ressourcen tätigen, müsste dann immer noch ein Finalizer implementiert werden?

  2. Wenn ich jedoch eine Klasse entwickle, die keine nicht verwaltete Ressource - direkt oder indirekt - verwendet, sollte ich die IDisposable damit die Clients dieser Klasse die "using-Anweisung" verwenden können?

    Wäre es machbar, IDisposable zu implementieren, nur um den Clients Ihrer Klasse die Verwendung der using-Anweisung zu ermöglichen?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. Ich habe den folgenden einfachen Code 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:

  1. Hier habe ich den Finalizer nicht hinzugefügt, und normalerweise wird der Finalizer vom GC aufgerufen, und der Finalizer ruft den Dispose auf. Da ich den Finalizer nicht habe, wann rufe ich die Dispose-Methode auf? Ist es der Client der Klasse, der sie aufrufen muss?

    Meine Klasse im Beispiel heißt also NoGateway und der Client könnte die Klasse wie folgt verwenden und entsorgen:

    using(NoGateway objNoGateway = new NoGateway())
    {
        // Do stuff here   
    }

    Wird die Dispose-Methode automatisch aufgerufen, wenn die Ausführung das Ende des using-Blocks erreicht, oder muss der Client die Dispose-Methode manuell aufrufen? d.h.

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
  2. Ich verwende die WebClient Klasse in meinem NoGateway Klasse. Denn WebClient implementiert die IDisposable Schnittstelle, bedeutet dies, dass WebClient indirekt nicht verwaltete Ressourcen verwendet? Gibt es eine feste Regel, um dies zu befolgen? Woher weiß ich, dass eine Klasse nicht verwaltete Ressourcen verwendet?

12voto

pm100 Punkte 40532

Niemand hat die Frage beantwortet, ob man IDisposable implementieren sollte, obwohl man es nicht braucht.

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 das tun? Die meisten Entwickler werden "using" nicht verwenden, es sei denn, sie wissen, dass sie es müssen - und woher wissen sie das? Entweder

  • seine obviuos die sie aus Erfahrung (eine Steckdose Klasse zum Beispiel)
  • seine dokumentierte
  • sind sie vorsichtig und können sehen, dass die Klasse IDisposable implementiert

Indem Sie also 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 Geltungsbereich des Objekts ist nicht lokal); und in diesen anderen Fällen müssen sie sich Gedanken über die Lebensdauer der Objekte machen - ich würde mir sicher Sorgen machen. Aber das ist nicht notwendig

Sie implementieren Idisposable, um ihnen die Verwendung von using zu ermöglichen, aber sie werden using nicht verwenden, es sei denn, Sie sagen es ihnen.

Tun Sie es also nicht

5voto

Andrei Krasutski Punkte 4105

Muster entsorgen:

public abstract class DisposableObject : IDisposable
{
    public bool Disposed { get; private set;}      

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

    ~DisposableObject()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (!Disposed)
        {
            if (disposing)
            {
                DisposeManagedResources();
            }

            DisposeUnmanagedResources();
            Disposed = true;
        }
    }

    protected virtual void DisposeManagedResources() { }
    protected virtual void DisposeUnmanagedResources() { }
}

Beispiel für Vererbung:

public class A : DisposableObject
{
    public Component components_a { get; set; }
    private IntPtr handle_a;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeManagedResources");
          components_a.Dispose();
          components_a = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeUnmanagedResources");
          CloseHandle(handle_a);
          handle_a = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

public class B : A
{
    public Component components_b { get; set; }
    private IntPtr handle_b;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeManagedResources");
          components_b.Dispose();
          components_b = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeUnmanagedResources");
          CloseHandle(handle_b);
          handle_b = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

4voto

Lasse V. Karlsen Punkte 364542
  1. Wenn Sie andere verwaltete Objekte verwenden, die nicht verwaltete Ressourcen nutzen, liegt es nicht in Ihrer Verantwortung, sicherzustellen, dass diese finalisiert sind. Es liegt in Ihrer Verantwortung, Dispose für diese Objekte aufzurufen, wenn Dispose für Ihr Objekt aufgerufen wird, und damit ist Schluss.

  2. Wenn Ihre Klasse keine knappen Ressourcen verwendet, sehe ich nicht ein, warum Sie Ihre Klasse IDisposable implementieren lassen sollten. Sie sollten dies nur tun, wenn Sie:

    • Sie wissen, dass Sie bald knappe Ressourcen in Ihren Objekten haben werden, nur nicht jetzt (und das meine ich im Sinne von "wir sind noch in der Entwicklung, es wird kommen, bevor wir fertig sind", nicht im Sinne von "ich glaube, wir werden das brauchen")
    • Knappe Ressourcen nutzen
  3. Ja, der Code, der Ihren Code verwendet, muss die Dispose-Methode Ihres Objekts aufrufen. Und ja, der Code, der Ihr Objekt verwendet, kann die using wie Sie gezeigt haben.

  4. (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 er IDisposable implementiert, und es liegt an Ihnen, dieses Wissen zu nutzen, indem Sie das Objekt entsorgen, wenn Sie damit fertig sind, auch wenn sich herausstellt, dass der WebClient überhaupt keine anderen Ressourcen verwendet.

4voto

Dave Black Punkte 6469

Einige Aspekte der eine andere Antwort sind aus 2 Gründen nicht ganz korrekt:

Erstens,

using(NoGateway objNoGateway = new NoGateway())

ist eigentlich gleichbedeutend mit:

try
{
    NoGateway = new NoGateway();
}
finally
{
    if(NoGateway != null)
    {
        NoGateway.Dispose();
    }
}

Dies mag lächerlich klingen, da der Operator "new" niemals "null" zurückgeben sollte, es sei denn, es liegt eine OutOfMemory-Ausnahme vor. Aber betrachten Sie 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 seiner Implementierung von IDisposable erben kann oder nicht - denken Sie daran, dass ich bei vielen Kunden gesehen habe, wie das IDisposable-Muster falsch implementiert wurde, indem Entwickler einfach eine Dispose()-Methode hinzugefügt haben, ohne von IDisposable zu erben (schlecht, schlecht, schlecht). Es könnte auch der Fall eintreten, dass eine IDisposable-Ressource von einer Eigenschaft oder Methode zurückgegeben wird (wieder schlecht, schlecht, schlecht - verschenken Sie nicht Ihre IDisposable-Ressourcen).

using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
    if (NoGateway != null)
    {
        ...

Wenn der "as"-Operator null zurückgibt (oder eine Eigenschaft oder Methode, die die Ressource zurückgibt), und Ihr Code im "using"-Block gegen "null" geschützt ist, wird Ihr Code nicht scheitern, wenn Sie versuchen, Dispose für ein null-Objekt aufzurufen, weil die "eingebaute" null-Prüfung funktioniert.

Der zweite Grund, warum Ihre Antwort nicht korrekt ist, liegt in der folgenden Formulierung:

Ein Finalizer wird aufgerufen, wenn der GC Ihr Objekt zerstört hat.

Erstens ist die Finalisierung (wie auch die GC selbst) nicht-deterministisch. Die CLR bestimmt, wann sie einen Finalizer aufruft, d.h. der Entwickler/Code hat keine Ahnung. Wenn das IDisposable-Muster korrekt implementiert ist (wie ich oben geschrieben habe) und GC.SuppressFinalize() aufgerufen wurde, wird der Finalizer NICHT aufgerufen. Dies ist einer der wichtigsten Gründe, das Muster korrekt zu implementieren. Da es nur einen Finalizer-Thread pro verwaltetem Prozess gibt, unabhängig von der Anzahl der logischen Prozessoren, kann man leicht die Leistung verschlechtern, indem man den Finalizer-Thread staut oder sogar hängen lässt, wenn man vergisst, GC.SuppressFinalize() aufzurufen.

Ich habe eine korrekte Implementierung des Dispose-Musters in meinem Blog veröffentlicht: Ordnungsgemäße Implementierung des Dispose-Musters

2voto

Jesse C. Slicer Punkte 19426

1) WebClient ist ein verwalteter Typ, so dass Sie keinen Finalizer benötigen. Der Finalizer wird für den Fall benötigt, dass Ihre Benutzer die Klasse NoGateway nicht mit Dispose() entsorgen und der native Typ (der nicht von der GC erfasst wird) anschließend aufgeräumt werden muss. In diesem Fall, wenn der Benutzer Dispose() nicht aufruft, wird der enthaltene WebClient von der GC direkt nach dem NoGateway entsorgt.

2) Indirekt ja, aber Sie sollten sich darüber keine Sorgen machen müssen. Ihr Code ist korrekt, und Sie können nicht verhindern, dass Ihre Benutzer Dispose() ganz einfach vergessen.

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