RAII Helperklassen in C#
-
hustbaer schrieb:
Wieso sollte/müsste sie es sein?
Aus meiner Sicht wäre es eher unsinnig, der Target-Property nachträglich etwas anderes zuzuweisen. Schließlich wird ein DisposeGuard ja nur über die Alloc-Methode erzeugt und kapselt hierbei ein bestimmtes Target.
-
Sehe ich ähnlich wie GPC. Denn wer stellt sicher, dass der vorherige Wert von Target wirklich korrekt Disposed wird?
Man stelle sich folgendes überspitztes Beispiel vor:
using(var guard = DisposeGuard.Alloc(new MemoryStream()) { .. // irgendwelche arbeiten using(FileStream fs = File.Open(path, FileMode.Open) { guard.Target = fs; // irgendwelche arbeiten } }
Es ist so nicht mehr sichergestellt, dass der MemoryStream korrekt Disposed wird. - Soetwas würde ich sofort unterbinden.
-
Ja, man kann damit Unfug bauen.
Man kann damit aber sowieso Unfug bauen, z.B. kann man immer
ReleaseTarget
zu früh aufrufen.Mein Ziel war nicht ein Hilfsmittel zu entwerfen das man möglichst nicht falsch verwenden kann, da ich der Meinung bin dass das hier gar nicht möglich ist.
Mein Ziel war ein Hilfsmittel zu entwerfen das man möglichst einfach und möglichst flexibel korrekt verwenden kann. Und dazu gehört mMn. auch dass man dasTarget
nachträglich zuweisen kann. Weil mMn. nicht viel Hirnschmalz dazugehört zu verstehen was für einen Effekt das hätte.Beispiel
Foo Fun() { using (var fooGuard = new DisposeGuard<Foo>()) { foreach (var stuff in m_stuff) { if (...) { fooGuard.Target = new Foo(stuff); break; } } if (fooGuard.Target == null) throw ...; DoSomeStuffWith(fooGuard.Target); return fooGuard.ReleaseTarget(); } }
Ich weiss, das kann man schöner lösen (z.B. über die LINQ Extension Methoden .Where und .First -- bzw. auch über eine eigene Hilfsfunktion), wobei dann auch die Notwendigkeit entfällt
.Target
nachträglich zuzuweisen. Ich wollte diese Art der Verwendung nur trotzdem nicht ganz ausschliessen.
-
Weiterer Anwendungsfall wo ich
DisposeGuard
häufig einsetze: Konstruktoren vonIDisposable
Klassen.class Foo : IDisposable { public Foo() { using (var disposeGuard = DisposeGuard.Alloc(this)) { m_disposableResource1 = ...; m_disposableResource2 = ...; m_disposableResource3 = ...; disposeGuard.ReleaseTarget(); } } }
-
@hustbear,
Mir ging es vor allem darum zu verstehen, ob das irgendeinen Zweck verfolgt. Wenn es keinen Zweck hat, dann würde ich es weglassen. Eben weil es nicht benötigt wird, wie du es selbst schreibst. Wozu diese Zuweisungsmöglichkeit drin lassen, wenn man damit nur Unsinn machen kann?
-
Weil mir, wie ich schon geschrieben habe, Fälle einfallen wo man es doch braucht. Ob man die umschreiben kann so dass man es doch wieder nicht braucht, ist dann wieder ne andere Frage
Aber egal. Meine Variante hat es drin. Wer die Klasse in eigene Projekte übernimmt kann den Setter ja weglassen - genau so wie das
Interlocked.Exchange
EDIT: und dasvolatile
/EDIT.
-
hustbaer schrieb:
Weil mir, wie ich schon geschrieben habe, Fälle einfallen wo man es doch braucht.
Kannst du Beispiele nennen? Rein aus Interesse, weil mir keine einfallen.
-
Naja z.B. wie schon oben skizziert.
Oder so Sachen wie
Thingy GetCoolestThingy() { using (var coolestThingyGuard = new DisposeGuard<Thingy>()) { foreach (...) { using (var someThingyGuard = DisposeGuard.Alloc(CreateSomeThingy(...))) { if (coolestThingyGuard.Target == null || IsCooler(someThingyGuard.Target, coolestThingyGuard.Target)) { coolestThingyGuard.Dispose(); coolestThingyGuard.Target = someThingyGuard.ReleaseTarget(); } } } // ... return coolestThingyGuard.ReleaseTarget(); } }
Auch das könnte man natürlich ohne "re-seatable" Target lösen -- z.B. indem man der
DisposeGuard<T>
Klasse eineSwap
Funktion verpasst.
Oder eine expliziteReplaceTarget
Funktion.
-
Ah sorry, ich bin anscheinend blind. Hab das vorherige Beispiel irgendwie übersehen. Aber ja, ich würde da schon eher eine Funktion hinpacken. Muss nicht mal ein Swap sein, könnte auch ein Realloc sein oder sowas.
public class DisposeGuard<T> { public void Realloc(T newTarget) { Dispose(); m_target = target; } }
Damit wäre gewährleistet, dass das alte
Target
sauber aufgeräumt wird. Wer das nicht möchte, kann immer noch vorher einReleaseTarget
aufrufen.Vielleicht wäre es hier aber dann auch sinnvoll eine Aufteilung zwischen
Dispose
undDisposeTarget
zu machen. Heisst Dispose ruftDisposeTarget
auf undRealloc
ruftDisposeTarget
auf. Nicht nötig, aber klarer. Aber gut, das sind Details.
-
Dravere schrieb:
Ah sorry, ich bin anscheinend blind.
Haha, macht nix, und sicher nicht so blind wie ich oft