Ist es missbräuchlich, IDisposable und "using" als Mittel zu verwenden, um "Scoped Behaviour" für die Ausnahmesicherheit zu erhalten?


111

Etwas, das ich in C ++ oft verwendet habe, war, eine Klasse über den Konstruktor und den Destruktor Aeine Zustandseintritts- und -ausgangsbedingung für eine andere Klasse behandeln zu lassen, um sicherzustellen, dass B einen bekannten Zustand hat, wenn etwas in diesem Bereich eine Ausnahme auslöst Umfang wurde verlassen. Dies ist kein reines RAII, was das Akronym betrifft, aber es ist dennoch ein etabliertes Muster.BA

In C # möchte ich oft tun

class FrobbleManager
{
    ...

    private void FiddleTheFrobble()
    {
        this.Frobble.Unlock();
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
        this.Frobble.Lock();
    }
}

Was so gemacht werden muss

private void FiddleTheFrobble()
{
    this.Frobble.Unlock();

    try
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
    finally
    {
        this.Frobble.Lock();
    }
}

wenn ich den FrobbleZustand bei der FiddleTheFrobbleRückkehr garantieren will . Der Code wäre schöner mit

private void FiddleTheFrobble()
{
    using (var janitor = new FrobbleJanitor(this.Frobble))
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
}

wo FrobbleJanitorsieht ungefähr so aus

class FrobbleJanitor : IDisposable
{
    private Frobble frobble;

    public FrobbleJanitor(Frobble frobble)
    {
        this.frobble = frobble;
        this.frobble.Unlock();
    }

    public void Dispose()
    {
        this.frobble.Lock();
    }
}

Und so will ich es machen. Jetzt Realität einholt, da das, was ich will , um den Einsatz erfordert , dass der FrobbleJanitorverwendet wird , mit using . Ich könnte dies als ein Problem bei der Codeüberprüfung betrachten, aber etwas nervt mich.

Frage: Würde das oben Gesagte als missbräuchliche Verwendung von usingund angesehen IDisposable?


23
+1 nur für die Klassen- und Methodennamen :-). Nun, um fair zu sein, und die eigentliche Frage.
Joey

2
@Johannes: Ja, ich kann verstehen warum - die meisten Leute fummeln nur an ihrer Geige herum, aber ich muss einfach dagegen protestieren. ;-) Und übrigens +1 für deine Namensähnlichkeit.
Johann Gerell

Vielleicht können Sie den Bereich lock (this) {..} verwenden, um in diesem Beispiel das gleiche Ergebnis zu erzielen?
o

1
@ oɔɯǝɹ: Sie verwenden lock (), um den gleichzeitigen Zugriff auf etwas von verschiedenen Threads aus zu verhindern. Mit dem von mir beschriebenen Szenario nichts zu tun.
Johann Gerell

1
IScopeable ohne Finalizer in der nächsten Version von C # :)
Jon Raynor

Antworten:


32

Ich denke nicht unbedingt. IDisposable technisch ist gemeint für Dinge verwendet werden , die nicht verwalteten Ressourcen haben, aber dann die using - Direktive ist nur eine nette Art und Weise ein gemeinsames Muster von der Implementierung try .. finally { dispose }.

Ein Purist würde argumentieren "Ja - es ist missbräuchlich", und im puristischen Sinne ist es; Aber die meisten von uns codieren nicht aus puristischer, sondern aus einer halbkünstlerischen Perspektive. Die Verwendung des Konstrukts "using" auf diese Weise ist meiner Meinung nach in der Tat ziemlich künstlerisch.

Sie sollten wahrscheinlich eine andere Schnittstelle auf IDisposable kleben, um sie etwas weiter wegzuschieben, und anderen Entwicklern erklären, warum diese Schnittstelle IDisposable impliziert.

Es gibt viele andere Alternativen dazu, aber letztendlich kann ich mir keine vorstellen, die so ordentlich sein wird, also machen Sie es!


2
Ich glaube, es ist auch eine künstlerische Verwendung von "Verwenden". Ich denke, dass Samual Jacks Beitrag, der auf einen Kommentar von Eric Gunnerson verweist, diese Implementierung von "using" bestätigt. Ich kann ausgezeichnete Punkte sehen, die sowohl von Andras als auch von Eric gemacht wurden.
IAbstract

1
Ich habe vor einiger Zeit mit Eric Gunnerson darüber gesprochen, und laut ihm war es die Absicht des C # -Design-Teams, die Verwendung von Bereichen zu kennzeichnen. Tatsächlich postulierte er, dass es möglicherweise nicht einmal eine Lock-Anweisung geben würde, wenn sie vor der Lock-Anweisung entworfen hätten. Es wäre ein using-Block mit einem Monitor-Aufruf oder so gewesen. UPDATE: Gerade wurde klar, dass die nächste Antwort von Eric Lippert stammt, der auch im Sprachteam ist. ;) Vielleicht ist sich das C # -Team selbst darüber nicht einig? Der Kontext meiner Diskussion mit Gunnerson war die TimedLock-Klasse: bit.ly/lKugIO
Haacked

2
@ Haacked - Amüsant, nicht wahr? Ihr Kommentar beweist völlig meinen Standpunkt; dass du mit einem Mann im Team gesprochen hast und er alles dafür war; Dann wird Eric Lippert vom selben Team darauf hingewiesen, dass er anderer Meinung ist. Aus meiner Sicht; C # bietet ein Schlüsselwort, das eine Schnittstelle ausnutzt und zufällig ein gut aussehendes Codemuster ergibt, das in so vielen Szenarien funktioniert. Wenn wir es nicht missbrauchen sollen, sollte C # einen anderen Weg finden, dies durchzusetzen. In jedem Fall müssen die Designer hier wahrscheinlich ihre Enten hintereinander bringen! In der Zwischenzeit: using(Html.BeginForm())Sir? Es macht mir nichts aus, wenn ich es tue, Sir! :)
Andras Zoltan

71

Ich betrachte dies als Missbrauch der using-Anweisung. Mir ist bewusst, dass ich in dieser Position in der Minderheit bin.

Ich betrachte dies aus drei Gründen als Missbrauch.

Erstens, weil ich davon ausgehe, dass "using" verwendet wird, um eine Ressource zu verwenden und zu entsorgen, wenn Sie damit fertig sind . Das Ändern des Programmstatus verwendet keine Ressource und das Zurücksetzen ändert nichts. Daher ist das "Verwenden" zum Mutieren und Wiederherstellen des Zustands ein Missbrauch. Der Code ist für den Gelegenheitsleser irreführend.

Zweitens, weil ich erwarte, dass "Verwenden" aus Höflichkeit und nicht aus Notwendigkeit verwendet wird . Der Grund, warum Sie "using" verwenden, um eine Datei zu entsorgen, wenn Sie damit fertig sind, liegt nicht darin, dass dies erforderlich ist , sondern darin, dass es höflich ist - möglicherweise wartet jemand anderes darauf, diese Datei zu verwenden, und sagt "erledigt" jetzt "ist die moralisch korrekte Sache zu tun. Ich gehe davon aus, dass ich in der Lage sein sollte, eine "Verwendung" umzugestalten, damit die verwendete Ressource länger aufbewahrt und später entsorgt wird, und dass die einzige Auswirkung darin besteht, andere Prozesse geringfügig zu stören . Ein "using" -Block, der semantische Auswirkungen auf den Programmstatus hat ist missbräuchlich, weil es eine wichtige, erforderliche Mutation des Programmzustands in einem Konstrukt verbirgt, das so aussieht, als ob es der Bequemlichkeit und Höflichkeit dient, nicht der Notwendigkeit.

Und drittens werden die Aktionen Ihres Programms durch seinen Status bestimmt. Die Notwendigkeit einer sorgfältigen Manipulation des Staates ist genau der Grund, warum wir dieses Gespräch überhaupt führen. Lassen Sie uns überlegen, wie wir Ihr ursprüngliches Programm analysieren könnten.

Wenn Sie dies zu einer Codeüberprüfung in meinem Büro bringen würden, würde ich als erstes die Frage stellen: "Ist es wirklich richtig, das Frobble zu sperren, wenn eine Ausnahme ausgelöst wird?" Aus Ihrem Programm geht eindeutig hervor, dass dieses Ding das Frobble aggressiv wieder sperrt, egal was passiert. Ist das richtig? Eine Ausnahme wurde ausgelöst. Das Programm befindet sich in einem unbekannten Zustand. Wir wissen nicht, ob Foo, Fiddle oder Bar geworfen haben, warum sie geworfen haben oder welche Mutationen sie in einem anderen Zustand durchgeführt haben, der nicht aufgeräumt wurde. Können Sie mich davon überzeugen , dass es in dieser schrecklichen Situation immer das Richtige ist, erneut zu sperren?

Vielleicht ist es das, vielleicht ist es das nicht. Mein Punkt ist, dass der Code-Prüfer mit dem Code, wie er ursprünglich geschrieben wurde, die Frage stellen kann . Mit dem Code, der "using" verwendet, weiß ich nicht, ob ich die Frage stellen soll. Ich gehe davon aus, dass der "using" -Block eine Ressource zuweist, sie für eine Weile verwendet und höflich darüber verfügt, wenn dies erledigt ist, und nicht, dass die schließende Klammer des "using" -Blocks meinen Programmstatus unter außergewöhnlichen Umständen mutiert, wenn beliebig viele Die Konsistenzbedingungen des Programmstatus wurden verletzt.

Die Verwendung des "using" -Blocks für einen semantischen Effekt macht dieses Programmfragment:

}

äußerst bedeutungsvoll. Wenn ich mir diese einzelne enge Zahnspange ansehe, denke ich nicht sofort, "dass diese Zahnspange Nebenwirkungen hat, die weitreichende Auswirkungen auf den globalen Status meines Programms haben". Aber wenn Sie "Verwenden" so missbrauchen, geschieht dies plötzlich.

Das zweite, was ich fragen würde, wenn ich Ihren Originalcode sehen würde, ist "Was passiert, wenn eine Ausnahme nach dem Entsperren ausgelöst wird, aber bevor der Versuch eingegeben wird?" Wenn Sie eine nicht optimierte Assembly ausführen, hat der Compiler möglicherweise vor dem Versuch eine No-Op-Anweisung eingefügt, und es ist möglich, dass beim No-Op eine Thread-Abbruch-Ausnahme auftritt. Dies ist selten, aber im wirklichen Leben, insbesondere auf Webservern. In diesem Fall erfolgt die Entsperrung, die Sperre jedoch nie, da die Ausnahme vor dem Versuch ausgelöst wurde. Es ist durchaus möglich, dass dieser Code für dieses Problem anfällig ist und tatsächlich geschrieben werden sollte

bool needsLock = false;
try
{
    // must be carefully written so that needsLock is set
    // if and only if the unlock happened:

    this.Frobble.AtomicUnlock(ref needsLock);
    blah blah blah
}
finally
{
    if (needsLock) this.Frobble.Lock();
}

Wieder vielleicht, vielleicht auch nicht, aber ich weiß, dass ich die Frage stellen muss . Bei der "using" -Version tritt das gleiche Problem auf: Eine Thread-Abbruch-Ausnahme kann ausgelöst werden, nachdem das Frobble gesperrt wurde, aber bevor der mit der Verwendung verknüpfte try-geschützte Bereich eingegeben wird. Aber mit der "using" -Version gehe ich davon aus, dass dies ein "na und?" Ist. Lage. Es ist bedauerlich, wenn das passiert, aber ich gehe davon aus, dass das "Verwenden" nur dazu da ist, höflich zu sein, nicht um den lebenswichtigen Programmzustand zu mutieren. Ich gehe davon aus, dass der Garbage Collector diese Ressource schließlich durch Ausführen des Finalizers bereinigt, wenn eine schreckliche Ausnahme für den Thread-Abbruch genau zum falschen Zeitpunkt auftritt.


18
Obwohl ich die Frage anders beantworten würde, denke ich, dass dies ein gut argumentierter Beitrag ist. Ich stelle jedoch Ihre Behauptung in Frage, dass usinges sich eher um Höflichkeit als um Korrektheit handelt. Ich glaube, dass Ressourcenlecks Korrektheitsprobleme sind, obwohl sie oft so gering sind, dass sie keine Fehler mit hoher Priorität sind. Somit haben usingBlöcke bereits semantische Auswirkungen auf den Programmstatus, und was sie verhindern, ist nicht nur "Unannehmlichkeit" für andere Prozesse, sondern möglicherweise eine kaputte Umgebung. Das heißt nicht, dass die unterschiedliche Art der semantischen Auswirkung, die die Frage impliziert, ebenfalls angemessen ist.
KVB

13
Ressourcenlecks sind Korrektheitsprobleme, ja. Wenn Sie jedoch kein "using" verwenden, wird die Ressource nicht verloren. Die Ressource wird schließlich bereinigt, wenn der Finalizer ausgeführt wird. Die Bereinigung ist möglicherweise nicht so aggressiv und zeitnah, wie Sie möchten, aber es wird passieren.
Eric Lippert

7
Wundervoller Beitrag, hier sind meine zwei Cent :) Ich vermute, dass viel "Missbrauch" daran usingliegt, dass es sich um einen aspektorientierten Weber eines armen Mannes in C # handelt. Was der Entwickler wirklich möchte, ist eine Möglichkeit, um sicherzustellen, dass ein allgemeiner Code am Ende eines Blocks ausgeführt wird, ohne dass dieser Code dupliziert werden muss. C # unterstützt AOP heute nicht (MarshalByRefObject & PostSharp nicht standhalten). Die Transformation der using-Anweisung durch den Compiler ermöglicht es jedoch, eine einfache AOP zu erreichen, indem die Semantik der deterministischen Ressourcenentsorgung neu definiert wird. Obwohl dies keine gute Übung ist, ist es manchmal attraktiv, darauf zu verzichten .....
LBushkin

11
Obwohl ich Ihrer Position im Allgemeinen zustimme, gibt es einige Fälle, in denen der Vorteil einer Umgestaltung usingzu attraktiv ist, um aufzugeben. Das beste Beispiel, das ich mir vorstellen kann, ist das Verfolgen und Berichten von Code-Timings: using( TimingTrace.Start("MyMethod") ) { /* code */ } Auch dies ist AOP - Start()erfasst die Startzeit vor Beginn des Blocks, Dispose()erfasst die Endzeit und protokolliert die Aktivität. Es ändert den Programmstatus nicht und ist unabhängig von Ausnahmen. Es ist auch attraktiv, weil es ziemlich viel Klempnerarbeit vermeidet und es häufig als Muster verwendet wird, um die verwirrende Semantik zu mildern.
LBushkin

6
Komisch, ich habe immer daran gedacht, RAII zu unterstützen. Es erscheint mir ziemlich intuitiv, ein Schloss als eine Art Ressource zu betrachten.
Brian

27

Wenn Sie nur sauberen Code mit Gültigkeitsbereich wünschen, können Sie auch Lambdas á la verwenden

myFribble.SafeExecute(() =>
    {
        myFribble.DangerDanger();
        myFribble.LiveOnTheEdge();
    });

wo die .SafeExecute(Action fribbleAction)Methode den try- catch- finallyBlock umschließt.


In diesem Beispiel haben Sie den Eingabestatus verpasst. Stellen Sie außerdem sicher, dass Sie try / finally einpacken, aber Sie rufen schließlich nichts an. Wenn also etwas im Lambda wirft, haben Sie keine Ahnung, in welchem ​​Zustand Sie sich befinden.
Johann Gerell

1
Ah! Ok, ich denke du meinst das SafeExecuteist eine FribbleMethode die aufruft Unlock()und Lock()im verpackten try / finally. Ich entschuldige mich dann. Ich sah sofort SafeExecuteals generische Erweiterungsmethode und erwähnte daher das Fehlen eines Ein- und Ausgangszustands. Mein Fehler.
Johann Gerell

1
Seien Sie sehr vorsichtig mit diesem Ansatz. Bei gefangenen Einheimischen kann es zu gefährlichen, subtilen, unbeabsichtigten Lebensverlängerungen kommen!
Jason

4
Yuk. Warum versteckst du try / catch / finally? Versteckt Ihren Code, um ihn für andere zu lesen.
Frank Schwieterman

2
@ Yukky Frank: Es war nicht meine Idee, etwas zu "verstecken", es war die Bitte des Fragestellers. :-P ... Das heißt, die Anfrage ist endlich eine Frage von "Wiederhole dich nicht". Möglicherweise haben Sie viele Methoden, die denselben "Rahmen" für das saubere Erfassen / Freigeben von Objekten erfordern, und Sie möchten dies dem Anrufer nicht aufzwingen (denken Sie an die Kapselung). Man könnte auch Methoden wie .SafeExecute (...) klarer benennen, um gut genug zu kommunizieren, was sie tun.
Herzmeister

24

Eric Gunnerson, der Mitglied des C # -Sprachendesign-Teams war, gab diese Antwort auf fast dieselbe Frage:

Doug fragt:

re: Eine Sperranweisung mit Timeout ...

Ich habe diesen Trick schon einmal gemacht, um mit gängigen Mustern in zahlreichen Methoden umzugehen . Normalerweise Sperrenerfassung , aber es gibt einige andere . Das Problem ist, dass es sich immer wie ein Hack anfühlt, da das Objekt nicht wirklich verfügbar ist, sondern " am Ende eines Bereichs zurückgerufen werden kann ".

Doug,

Als wir uns für die using-Anweisung entschieden haben, haben wir beschlossen, sie "using" zu nennen und nicht etwas Spezifischeres für die Entsorgung von Objekten, damit sie für genau dieses Szenario verwendet werden kann.


4
Nun, dieses Zitat sollte sagen, worauf er sich bezog, als er "genau dieses Szenario" sagte, da ich bezweifle, dass er sich auf diese zukünftige Frage bezog.
Frank Schwieterman

3
@ Frank Schwieterman: Ich habe das Zitat vervollständigt. Offenbar sind die Menschen aus dem C # Team taten denken , das usingwurde Feature nicht auf Ressourcen zur Verfügung zu beschränken.
Paercebal

11

Es ist ein rutschiger Hang. IDisposable hat einen Vertrag, der von einem Finalizer gesichert wird. Ein Finalizer ist in Ihrem Fall nutzlos. Sie können den Client nicht zwingen, die using-Anweisung zu verwenden, sondern ihn nur dazu ermutigen. Sie können es mit einer Methode wie der folgenden erzwingen:

void UseMeUnlocked(Action callback) {
  Unlock();
  try {
    callback();
  }
  finally {
    Lock();
  }
}

Aber das wird ohne Lamdas etwas unangenehm. Trotzdem habe ich IDisposable wie Sie verwendet.

Es gibt jedoch ein Detail in Ihrem Beitrag, das dies gefährlich nahe an ein Anti-Muster bringt. Sie haben erwähnt, dass diese Methoden eine Ausnahme auslösen können. Dies kann der Anrufer nicht ignorieren. Er kann drei Dinge dagegen tun:

  • Tun Sie nichts, die Ausnahme kann nicht wiederhergestellt werden. Der Normalfall. Das Aufrufen von Unlock spielt keine Rolle.
  • Fangen Sie die Ausnahme ab und behandeln Sie sie
  • Stellen Sie den Status in seinem Code wieder her und lassen Sie die Ausnahme die Aufrufkette passieren.

Bei den beiden letzteren muss der Aufrufer explizit einen try-Block schreiben. Jetzt stört die using-Anweisung. Es kann durchaus dazu führen, dass ein Klient ins Koma fällt, was ihn glauben lässt, dass Ihre Klasse sich um den Staat kümmert und keine zusätzliche Arbeit geleistet werden muss. Das ist fast nie richtig.


Der erzwungene Fall wurde oben von "herzmeister der welten" gegeben, denke ich - auch wenn das OP das Beispiel nicht zu mögen schien.
Benjamin Podszun

Ja, ich habe seine SafeExecuteals generische Erweiterungsmethode interpretiert . Ich sehe jetzt, dass es wahrscheinlich als eine FribbleMethode gemeint war , die aufruft Unlock()und Lock()im verpackten try / finally. Mein Fehler.
Johann Gerell

Das Ignorieren einer Ausnahme bedeutet nicht, dass sie nicht wiederhergestellt werden kann. Dies bedeutet, dass es oben im Stapel behandelt wird. Beispielsweise können Ausnahmen verwendet werden, um einen Thread ordnungsgemäß zu beenden. In diesem Fall müssen Sie Ihre Ressourcen, einschließlich gesperrter Sperren, korrekt freigeben.
Paercebal

7

Ein Beispiel aus der Praxis ist das BeginForm von ASP.net MVC. Grundsätzlich kann man schreiben:

Html.BeginForm(...);
Html.TextBox(...);
Html.EndForm();

oder

using(Html.BeginForm(...)){
    Html.TextBox(...);
}

Html.EndForm ruft Dispose auf, und Dispose gibt nur das </form>Tag aus. Das Gute daran ist, dass die {} Klammern einen sichtbaren "Bereich" erstellen, der es einfacher macht zu sehen, was sich im Formular befindet und was nicht.

Ich würde es nicht überbeanspruchen, aber im Wesentlichen ist IDisposable nur eine Möglichkeit zu sagen: "Sie MÜSSEN diese Funktion aufrufen, wenn Sie damit fertig sind." MvcForm verwendet es, um sicherzustellen, dass das Formular geschlossen ist. Stream verwendet es, um sicherzustellen, dass der Stream geschlossen ist. Sie können es verwenden, um sicherzustellen, dass das Objekt entsperrt ist.

Persönlich würde ich es nur verwenden, wenn die folgenden zwei Regeln wahr sind, aber die von mir willkürlich festgelegt werden:

  • Dispose sollte eine Funktion sein, die immer ausgeführt werden muss, daher sollte es keine Bedingungen außer Null-Checks geben
  • Nach Dispose () sollte das Objekt nicht wiederverwendbar sein. Wenn ich ein wiederverwendbares Objekt haben möchte, würde ich es lieber öffnen / schließen als entsorgen. Ich löse also eine InvalidOperationException aus, wenn ich versuche, ein entsorgtes Objekt zu verwenden.

Am Ende dreht sich alles um Erwartungen. Wenn ein Objekt IDisposable implementiert, gehe ich davon aus, dass es bereinigt werden muss, also nenne ich es. Ich denke, es ist normalerweise besser als eine "Shutdown" -Funktion.

Davon abgesehen mag ich diese Zeile nicht:

this.Frobble.Fiddle();

Da der FrobbleJanitor den Frobble jetzt "besitzt", frage ich mich, ob es nicht besser wäre, stattdessen Fiddle auf dem Frobble im Hausmeister anzurufen?


Über Ihr "Ich mag diese Zeile nicht" - ich habe tatsächlich kurz darüber nachgedacht, es bei der Formulierung der Frage so zu machen, aber ich wollte die Semantik meiner Frage nicht durcheinander bringen. Aber ich stimme dir irgendwie zu. So'ne Art. :)
Johann Gerell

1
Upvoted für die Bereitstellung eines Beispiels von Microsoft selbst. Beachten Sie, dass in dem von Ihnen genannten Fall eine bestimmte Ausnahme ausgelöst werden muss: ObjectDisposedException.
Antitoon

4

Wir haben dieses Muster in unserer Codebasis häufig verwendet, und ich habe es schon überall gesehen - ich bin sicher, dass es auch hier besprochen worden sein muss. Im Allgemeinen sehe ich nicht, was daran falsch ist, es liefert ein nützliches Muster und verursacht keinen wirklichen Schaden.


4

Ich stimme dem zu: Ich stimme den meisten hier zu, dass dies fragil, aber nützlich ist. Ich möchte Sie auf die System.Transaction.TransactionScope- Klasse verweisen , die so etwas tut, wie Sie es möchten.

Im Allgemeinen mag ich die Syntax, sie entfernt viel Unordnung aus dem echten Fleisch. Bitte geben Sie der Hilfsklasse einen guten Namen - vielleicht ... Umfang, wie im obigen Beispiel. Der Name sollte verraten, dass er einen Code einschließt. * Scope, * Block oder ähnliches sollte es tun.


1
Der "Hausmeister" stammt aus einem alten C ++ - Artikel von Andrei Alexandrescu über Scope Guards, noch bevor ScopeGuard seinen Weg nach Loki fand.
Johann Gerell

@ Benjamin: Ich mag die TransactionScope-Klasse, hatte noch nie davon gehört. Vielleicht, weil ich im .Net CF-Land bin, wo es nicht enthalten ist ... :-(
Johann Gerell

4

Hinweis: Mein Standpunkt ist wahrscheinlich von meinem C ++ - Hintergrund abhängig, daher sollte der Wert meiner Antwort anhand dieser möglichen Abweichung bewertet werden ...

Was sagt die C # -Sprachspezifikation?

Zitieren der C # -Sprachspezifikation :

8.13 Die using-Anweisung

[...]

Eine Ressource ist eine Klasse oder Struktur, die System.IDisposable implementiert, die eine einzelne parameterlose Methode namens Dispose enthält. Code, der eine Ressource verwendet, kann Dispose aufrufen, um anzuzeigen, dass die Ressource nicht mehr benötigt wird. Wenn Dispose nicht aufgerufen wird, erfolgt die automatische Entsorgung möglicherweise als Folge der Speicherbereinigung.

Der Code, der eine Ressource verwendet, ist natürlich der Code, der mit dem usingSchlüsselwort beginnt und bis zu dem Bereich reicht, der an das angehängt ist using.

Ich denke, das ist in Ordnung, weil das Schloss eine Ressource ist.

Vielleicht wurde das Schlüsselwort usingschlecht gewählt. Vielleicht hätte es heißen sollen scoped.

Dann können wir fast alles als Ressource betrachten. Ein Dateihandle. Eine Netzwerkverbindung ... Ein Thread?

Ein Thread ???

Das usingSchlüsselwort verwenden (oder missbrauchen) ?

Wäre es glänzend , das usingSchlüsselwort (ab) zu verwenden , um sicherzustellen, dass die Arbeit des Threads beendet ist, bevor der Bereich verlassen wird?

Herb Sutter scheint zu denken, dass es glänzend ist , da er eine interessante Verwendung des IDispose-Musters anbietet, um auf das Ende der Arbeit eines Threads zu warten:

http://www.drdobbs.com/go-parallel/article/showArticle.jhtml?articleID=225700095

Hier ist der Code, der aus dem Artikel kopiert wurde:

// C# example
using( Active a = new Active() ) {    // creates private thread
       
       a.SomeWork();                  // enqueues work
       
       a.MoreWork();                   // enqueues work
       
} // waits for work to complete and joins with private thread

Während der C # -Code für das aktive Objekt nicht bereitgestellt wird, wird geschrieben, dass das C # das IDispose-Muster für den Code verwendet, dessen C ++ - Version im Destruktor enthalten ist. Wenn wir uns die C ++ - Version ansehen, sehen wir einen Destruktor, der darauf wartet, dass der interne Thread beendet wird, bevor er beendet wird, wie in diesem anderen Auszug des Artikels gezeigt:

~Active() {
    // etc.
    thd->join();
 }

Für Herb ist es also glänzend .


3

Ich glaube, die Antwort auf Ihre Frage ist nein, dies wäre kein Missbrauch von IDisposable.

Ich verstehe die IDisposableSchnittstelle so, dass Sie nach der Entsorgung nicht mehr mit einem Objekt arbeiten sollen (außer dass Sie seine DisposeMethode so oft aufrufen dürfen, wie Sie möchten).

Da Sie jedes Mal, wenn Sie zur Anweisung gelangen, explizit ein neues Objekt erstellen , verwenden Sie dasselbe Objekt niemals zweimal. Und da der Zweck darin besteht, ein anderes Objekt zu verwalten, erscheint dies für die Aufgabe der Freigabe dieser ("verwalteten") Ressource angemessen.FrobbleJanitorusingFrobbeJanitorDispose

(Übrigens Disposeschlägt der Standard-Beispielcode, der die ordnungsgemäße Implementierung von fast immer demonstriert, vor, dass auch verwaltete Ressourcen freigegeben werden sollten, nicht nur nicht verwaltete Ressourcen wie Dateisystemhandles.)

Das einzige, worüber ich mir persönlich Sorgen machen würde, ist, dass es weniger klar ist, was passiert, using (var janitor = new FrobbleJanitor())als mit einem expliziteren try..finallyBlock, in dem die Lock& Unlock-Operationen direkt sichtbar sind. Aber welcher Ansatz gewählt wird, hängt wahrscheinlich von den persönlichen Vorlieben ab.


1

Es ist nicht missbräuchlich. Sie verwenden sie, wofür sie erstellt wurden. Möglicherweise müssen Sie jedoch je nach Ihren Anforderungen aufeinander eingehen. Wenn Sie sich beispielsweise für 'Artistik' entscheiden, können Sie 'using' verwenden. Wenn Ihr Code jedoch häufig ausgeführt wird, können Sie aus Leistungsgründen die Konstrukte 'try' .. 'finally' verwenden. Weil 'Verwenden' normalerweise das Erstellen eines Objekts beinhaltet.


1

Ich denke du hast es richtig gemacht. Das Überladen von Dispose () wäre ein Problem, das dieselbe Klasse später tatsächlich bereinigen musste, und die Lebensdauer dieser Bereinigung änderte sich anders als die Zeit, zu der Sie voraussichtlich eine Sperre halten. Da Sie jedoch eine separate Klasse (FrobbleJanitor) erstellt haben, die nur für das Sperren und Entsperren des Frobble verantwortlich ist, sind die Dinge so weit entkoppelt, dass Sie dieses Problem nicht lösen.

Ich würde FrobbleJanitor jedoch umbenennen, wahrscheinlich in etwas wie FrobbleLockSession.


Informationen zum Umbenennen - Ich habe "Hausmeister" als Hauptgrund für das Sperren / Entsperren verwendet. Ich dachte, es reimt sich auf die anderen Namenswahlen. ;-) Wenn ich diese Methode als Muster in meiner gesamten Codebasis verwenden würde, würde ich definitiv etwas allgemeiner Beschreibendes einschließen, wie Sie mit "Sitzung" angedeutet haben. Wenn dies die alten C ++ - Tage gewesen wären, würde ich wahrscheinlich FrobbleUnlockScope verwenden.
Johann Gerell
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.