Etwas, das ich in C ++ oft verwendet habe, war, eine Klasse über den Konstruktor und den Destruktor A
eine 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.B
A
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 Frobble
Zustand bei der FiddleTheFrobble
Rü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 FrobbleJanitor
sieht 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 FrobbleJanitor
verwendet 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 using
und angesehen IDisposable
?
quelle
Antworten:
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!
quelle
using(Html.BeginForm())
Sir? Es macht mir nichts aus, wenn ich es tue, Sir! :)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
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.
quelle
using
es 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 habenusing
Blö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.using
liegt, 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 .....using
zu 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.Wenn Sie nur sauberen Code mit Gültigkeitsbereich wünschen, können Sie auch Lambdas á la verwenden
wo die
.SafeExecute(Action fribbleAction)
Methode dentry
-catch
-finally
Block umschließt.quelle
SafeExecute
ist eineFribble
Methode die aufruftUnlock()
undLock()
im verpackten try / finally. Ich entschuldige mich dann. Ich sah sofortSafeExecute
als generische Erweiterungsmethode und erwähnte daher das Fehlen eines Ein- und Ausgangszustands. Mein Fehler.Eric Gunnerson, der Mitglied des C # -Sprachendesign-Teams war, gab diese Antwort auf fast dieselbe Frage:
quelle
using
wurde Feature nicht auf Ressourcen zur Verfügung zu beschränken.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:
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:
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.
quelle
SafeExecute
als generische Erweiterungsmethode interpretiert . Ich sehe jetzt, dass es wahrscheinlich als eineFribble
Methode gemeint war , die aufruftUnlock()
undLock()
im verpackten try / finally. Mein Fehler.Ein Beispiel aus der Praxis ist das BeginForm von ASP.net MVC. Grundsätzlich kann man schreiben:
oder
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:
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:
Da der FrobbleJanitor den Frobble jetzt "besitzt", frage ich mich, ob es nicht besser wäre, stattdessen Fiddle auf dem Frobble im Hausmeister anzurufen?
quelle
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.
quelle
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.
quelle
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 :
Der Code, der eine Ressource verwendet, ist natürlich der Code, der mit dem
using
Schlüsselwort beginnt und bis zu dem Bereich reicht, der an das angehängt istusing
.Ich denke, das ist in Ordnung, weil das Schloss eine Ressource ist.
Vielleicht wurde das Schlüsselwort
using
schlecht gewählt. Vielleicht hätte es heißen sollenscoped
.Dann können wir fast alles als Ressource betrachten. Ein Dateihandle. Eine Netzwerkverbindung ... Ein Thread?
Ein Thread ???
Das
using
Schlüsselwort verwenden (oder missbrauchen) ?Wäre es glänzend , das
using
Schlü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:
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:
Für Herb ist es also glänzend .
quelle
Ich glaube, die Antwort auf Ihre Frage ist nein, dies wäre kein Missbrauch von
IDisposable
.Ich verstehe die
IDisposable
Schnittstelle so, dass Sie nach der Entsorgung nicht mehr mit einem Objekt arbeiten sollen (außer dass Sie seineDispose
Methode 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.
FrobbleJanitor
using
FrobbeJanitor
Dispose
(Übrigens
Dispose
schlä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 expliziterentry..finally
Block, in dem dieLock
&Unlock
-Operationen direkt sichtbar sind. Aber welcher Ansatz gewählt wird, hängt wahrscheinlich von den persönlichen Vorlieben ab.quelle
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.
quelle
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.
quelle