Alternativen zu geschachtelten Using Statements?
-
Danke für eure Hilfe!
Das mit using-Statements gruppieren ist eine gute Sache. Werd ich in Zukunft benutzen.
Dravere schrieb:
Du benötigst auch das
zipStream
Objekt gar nicht. Bau dein Code daher am besten gleich etwas um:using(var reader = new BinaryReader(entry.OpenReader())) { // ... }
wenn entry.OpenReader funktioniert, aber new BinaryReader z.B. eine OutOfMemoryException wirft, dann wird der reader nicht geschlossen, oder?
Ist zwar ziemlich unwahrscheinlich, aber gerade im c++ forum wird ja immer viel wert auf exceptionsicherheit gelegtDas mit Aufteilung in Methoden ist mir natürlich auch klar. Ich wollte nur eben mal quick&dirty nen kurzes programm schreiben. Als nächstes wäre ich jetzt erstmal mit Refactor->Extract Method darauf losgegangen.
Ich hab übrigens noch folgendes rausgefunden:
C# hat mit stackalloc sogar die Möglichkeit, etwas auf dem stack anzulegen. Ich finds nur sehr schade, dass das nicht gleichzeitig auch zu einem automatischen Dispose führt.
-
Q schrieb:
wenn entry.OpenReader funktioniert, aber new BinaryReader z.B. eine OutOfMemoryException wirft, dann wird der reader nicht geschlossen, oder?
Ist zwar ziemlich unwahrscheinlich, aber gerade im c++ forum wird ja immer viel wert auf exceptionsicherheit gelegtMusst du gar kein Smilie hinsetzen, der Einwand ist durchaus gerechtfertig. Obwohl ich auch der Meinung bin, dass die Wahrscheinlichkeit eher klein ist. Aber das ist natürlich keine Rechtfertigung für den Fehler.
Q schrieb:
Ich hab übrigens noch folgendes rausgefunden:
C# hat mit stackalloc sogar die Möglichkeit, etwas auf dem stack anzulegen. Ich finds nur sehr schade, dass das nicht gleichzeitig auch zu einem automatischen Dispose führt.stackalloc
ist auch nur für unsafe Code gedacht. Beistackalloc
geht es nur um zwei Dinge: Interoperabilität und Performance. Du kannst im übrigenstackalloc
auch gar nicht mit Referenztypen verwenden.
Schieb daher diese Erkenntnis lieber ziemlich weit nach hinten in deinem Kopf.stackalloc
ist etwas, was du kaum je einmal benutzen wirst.Grüssli
-
z.T. stackalloc:
Wenn der Compiler sieht dass ein Objekt beim verlassen eines Scopes automatisch "unreachable" wird, dann legt er es doch selbst auf den Stack, oder nicht?
Also auch "class" Typen.Zumindest hab ich das so in Erinnerung.
Könnte nur sein dass ich wo gelesen habe dass das eine mögliche Optimierung wäre, die aber nicht umgesetzt wurde.
-
hustbaer schrieb:
z.T. stackalloc:
Wenn der Compiler sieht dass ein Objekt beim verlassen eines Scopes automatisch "unreachable" wird, dann legt er es doch selbst auf den Stack, oder nicht?
Also auch "class" Typen.Nein. Wäre mir zumindest neu und habe es zur Sicherheit auch schnell in einem einfachen Programm getestet, macht mein MS C# 4.0 Kompiler nicht.
Könnte es sein, dass du das damit verwechselst, dass die Referenz auf das Objekt selber natürlich auf dem Stack angelegt wird. Und die Value-Typen werden natürlich trotz
new
auf dem Stack angelegt.Edit: Gut, was natürlich sein könnte ist, dass der JIT dies dann so umbaut, aber daran zweifle ich etwas.
Grüssli
-
Nene, ich meine schon das Objekt selbst, und nicht die Referenz.
Hab auch grad ein wenig gegoogelt. Das ganze nennt sich "escape analysis", und .NET macht es anscheinend (noch?) nicht. Schade.
-
hustbaer schrieb:
(noch?)
Hast du etwas darüber gelesen, dass dies überhaupt in Erwägung gezogen wird für C#?
Soweit mir bekannt ist, gibt es ja dieses Feature in der JVM. Allerdings frage ich mich, ob man das dort nicht eingeführt hat, um eben das Fehlen von eigenen Value Typen auszugleichen. Bzw. dass man in C# deswegen dies gar nicht als nötig oder irgendwie wichtig sieht.
Grüssli
-
Nö, hab dazu nix gelesen.
Wäre aber eine schöne Optimierung.
Und öffnet den Weg zu weiteren schönen Optimierungen, z.B. Lock-Elision.mMn. müsste das auch halbwegs einfach zu machen sein.
Würde mich daher nicht wundern wenn das noch irgendwann kommt.
Wäre auch für C++ eine feine Sache. Dummerweise erlaubt es der aktuelle Standard AFAIK nicht, da dort glaube ich vorgeschrieben ist dass, wenn die Klasse keinen eigenen definiert, der globale
operator new
aufgerufen wird. Da man den selbst definieren darf, muss man den Aufruf als beobachtbar werten, und der Compiler darf den Aufruf nicht mehr einfach so wegoptimieren.
Wobei es in C++ wohl weniger wichtig ist, da man dort mit wenigernew
auskommt.
-
I heard you like IDisposable. So i put some IDisposables in a IDisposable
using System; using System.Collections.Generic; namespace CleanUp { class Program { static void Main(string[] args) { A a = new A(); B b = new B(); B b2 = new B(); A a2 = new A(); using (Disposables dispo = new Disposables { a, b, b2, a2 }) { a.A_func(); b.B_func(); b2.B_func(); a2.A_func(); } Console.ReadKey(true); } } class Disposables : IDisposable, IEnumerable<IDisposable> { List<IDisposable> disposables = new List<IDisposable>(); public void Dispose() { disposables.Reverse(); foreach (var disposable in disposables) disposable.Dispose(); } public void Add(IDisposable disposable) { disposables.Add(disposable); } public IEnumerator<IDisposable> GetEnumerator() { return disposables.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return disposables.GetEnumerator(); } } class A : IDisposable { public void A_func() { Console.WriteLine("A_func"); } public void Dispose() { Console.WriteLine("Dispose A"); } } class B : IDisposable { public void B_func() { Console.WriteLine("B_func"); } public void Dispose() { Console.WriteLine("Dispose B"); } } }
-
@µ: Wir nennen es : *mit Tiefer Stimme sag*The Ultimate Disposer!
-
Schöne Idee, aber leider hats auch das Problem das es nicht mehr exceptionsicher ist (zumindest so wie du es hier verwendet hast).
A a = new A(); B b = new B(); B b2 = new B(); A a2 = new A(); using (Disposables dispo = new Disposables { a, b, b2, a2 }) { a.A_func(); b.B_func(); b2.B_func(); a2.A_func(); }
wenn new B()
oder new A()
schmeißen, werden die schon vorher erzeugten objekte nicht freigegeben.Evtl. wäre es so besser:
using(Disposer disposer = new Disposer()) { A a = new A(); disposer.Add(a); B b = new B(); disposer.Add(b); //... }
Wobei das Add auch wieder schmeißen könnte
Aber ich benutze jetzt einfach die Variante bei der die inneren usings die direkt auf ein äußeres folgen nicht weiter eingerückt werden.
-
Q schrieb:
Schöne Idee, aber leider hats auch das Problem das es nicht mehr exceptionsicher ist (zumindest so wie du es hier verwendet hast).
Exceptions die in einem Konstruktor geworfen werden, übergehen sowieso den finally-Block.
-
Ich würde das wenn dann eher so machen:
class Disposer : IDisposable, IEnumerable<IDisposable> { List<IDisposable> m_disposables = new List<IDisposable>(); public void Dispose() { if (m_disposables.Count > 0) { // don't use .Reverse() so that double-disposing won't lead to strange behavior for (int i = m_disposables.Count - 1; i >= 0; i--) { // Dispose() shouldn't throw, but it might, and we need to dispose everything that was added // so just catch and ignore all exceptions try { m_disposables[i].Dispose(); } catch { } } } } public T Add<T>(T disposable) { Add(disposable as IDisposable); return disposable; } public IDisposable Add(IDisposable disposable) { try { if (disposable != null) m_disposables.Add(disposable); return disposable; } catch { disposable.Dispose(); throw; } } public IEnumerator<IDisposable> GetEnumerator() { return m_disposables.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return m_disposables.GetEnumerator(); } }
Und verwenden ala
using (Disposer dispo = new Disposer()) { A a = dispo.Add(new A()); B b = dispo.Add(new B()); B b2 = dispo.Add(new B()); A a2 = dispo.Add(new A()); // ... do stuff ... }
-
wollt ihr euch gegenseitig veräppeln? das kann doch unmöglich ernst gemeint sein
-
µ schrieb:
Exceptions die in einem Konstruktor geworfen werden, übergehen sowieso den finally-Block.
Wie bitte? Oder versteh ich dich hier grad nicht richtig? Hast du dazu Quellen oder kannst das etwas genauer erläutern?
@hustbaer,
Ehm ... ja. Hast du sowas jemals schon eingesetzt?Das Ignorieren der Exception find ich aber mal eine ganz schlechte Idee. Du könntest zumindest ein rethrow der letzten Exception machen, nachdem alle
Dispose
Methoden aufgerufen wurden. Oder vielleicht sogar eine neue Exception werfen, welche auf die Exception hinweist: DiposeException?Grüssli
-
Nächster Versuch. Hatte gestern nach Qs Einwand noch folgendes im Sinn. Die Exceptions gehen nicht verloren.
Sieht jemand Schwachstellen?using System; using System.Collections.Generic; namespace CleanUp { class Program { static void Main(string[] args) { try { using (Disposables dispo = new Disposables()) { A a = dispo.Create<A>(); B b = dispo.Create<B>("hurr"); C c = dispo.Create<C>(a); a.A_func(); b.B_func(); } } catch(Exception ex) { Console.WriteLine(ex.InnerException.Message); } Console.ReadKey(true); } } class Disposables : IDisposable { List<IDisposable> disposables = new List<IDisposable>(); public T Create<T>() where T : IDisposable, new() { T t = new T(); disposables.Add(t); return t; } public T Create<T>(params object[] args) where T : IDisposable { T t = (T)Activator.CreateInstance(typeof(T), args); disposables.Add(t); return t; } public void Dispose() { disposables.Reverse(); foreach (var disposable in disposables) disposable.Dispose(); } } class A : IDisposable { public void A_func() { Console.WriteLine("A_func"); } public void Dispose() { Console.WriteLine("Dispose A"); } } class B : IDisposable { string s; public B(string s) { this.s = s; } public void B_func() { Console.WriteLine("B_func: {0}", s); } public void Dispose() { Console.WriteLine("Dispose B"); } } class C : IDisposable { public C(A a) { throw new Exception("exception in C"); } public void Dispose() { Console.WriteLine("Dispose C"); } } }
class Q : IDisposable { public Q() { throw new Exception(); } public void Dispose() { Console.WriteLine("Dispose Q"); } }
Dann wird bei
using (Q q = new Q()) { }
Dispose nicht aufgerufen. Klar, das Objekt wurde ja nicht vollständig konstruiert.
Hat aber nicht direkt etwas mit dem Einwand des Ops zu tun. Schieben wir es auf die Übermüdung
-
Wobei ich an hustbaers Vorschlag auch kein großes Problem sehe. Das try-catch in Add() hat irgendwie keinen Sinn, warum sollte dort eine Exception auftreten können?
Und eine kummulative Exception in Dispose bauen, dann sollte alles sauber sein.
-
µ schrieb:
Sieht jemand Schwachstellen?
Activator.CreateInstance kann in der Masse schon starke Performanceeinbußen nach sich ziehen.
http://bloggingabout.net/blogs/vagif/archive/2010/04/02/don-t-use-activator-createinstance-or-constructorinfo-invoke-use-compiled-lambda-expressions.aspx
-
µ schrieb:
Das try-catch in Add() hat irgendwie keinen Sinn, warum sollte dort eine Exception auftreten können?
OutOfMemoryException
----
Dravere schrieb:
@hustbaer,
Ehm ... ja. Hast du sowas jemals schon eingesetzt?Nicht genau in der Form. Wieso?
Das Ignorieren der Exception find ich aber mal eine ganz schlechte Idee.
Naja...
Dispose Exceptions werfen zu lassen ist ca. eine so gute Idee wie in C++ einen Destruktor eine Exception werfen zu lassen.Du könntest zumindest ein rethrow der letzten Exception machen, nachdem alle
Dispose
Methoden aufgerufen wurden.Das hatte ich mir auch überlegt. Hab' mich aber dann dagegen entschieden. Wie gesagt sollte mMn. Dispose() niemals eine Exception werfen. Wenn jetzt jemand fieserweise doch eine Exception aus Dispose() wirft, muss ich diese Gemeinheit nicht unbedingt an Clients meiner Klasse weitergeben.
Oder vielleicht sogar eine neue Exception werfen, welche auf die Exception hinweist: DiposeException?
Was macht eigentlich das .NET Framework intern?
Bei den Forms gibt's ja etliche Klassen die ne Collection von IDisposables haben, und das eigene Dispose an diese "weitergeben".
-
µ schrieb:
Sieht jemand Schwachstellen?
Deine Klasse kann man nicht mit dem Beispiel von Q verwenden. Er ruft eine Funktion auf (
OpenReader
) und bekommt da ein Objekt zurück, welches er aufräumen muss. Heisst deine Lösung ist mit dem Factory-Pattern nicht kompatibel.µ schrieb:
Dann wird bei
using (Q q = new Q()) { }
Dispose nicht aufgerufen. Klar, das Objekt wurde ja nicht vollständig konstruiert.
Hat aber nicht direkt etwas mit dem Einwand des Ops zu tun. Schieben wir es auf die ÜbermüdungAchso, das meintest du. Dann habe ich dich eindeutig falsch verstanden
hustbaer schrieb:
Dravere schrieb:
@hustbaer,
Ehm ... ja. Hast du sowas jemals schon eingesetzt?Nicht genau in der Form. Wieso?
In der Form sehe ich es schon als etwas fragwürdig an. Dass ein Objekt mehrere Objekte verwaltet und diese dann bei Dispose freigibt, ist natürlich etwas anderes. Aber nur um z.B. die Verschachtelung zu verkleinern?
hustbaer schrieb:
Dispose Exceptions werfen zu lassen ist ca. eine so gute Idee wie in C++ einen Destruktor eine Exception werfen zu lassen.
hustbaer schrieb:
Wie gesagt sollte mMn. Dispose() niemals eine Exception werfen.
Damit bin ich absolut einverstanden. Allerdings halte ich es genauso für falsch, Exceptions einfach zu schlucken und nicht zu behandeln. Sowas kann böse ins Auge gehen.
hustbaer schrieb:
Wenn jetzt jemand fieserweise doch eine Exception aus Dispose() wirft, muss ich diese Gemeinheit nicht unbedingt an Clients meiner Klasse weitergeben.
Du probierst also eine Fehlentscheidung im Design mit einer weiteren Fehlentscheidung zu korrigieren, zumindest meiner Meinung nach. Diesen Fehler kann man meiner Meinung nach allerdings nicht korrigieren. Wenn jemand so eine Klasse verwendet, dann wird er wohl darüber informiert sein, dass dies passieren kann oder wird es dann bei der Benutzung mitbekommen. Du verschlimmerst den Designfehler nur noch, wenn du die Exception nicht weitergibst.
hustbaer schrieb:
Was macht eigentlich das .NET Framework intern?
Bei den Forms gibt's ja etliche Klassen die ne Collection von IDisposables haben, und das eigene Dispose an diese "weitergeben".Ich habe mir gerade mit ILSpy mal die
System.Windows.Forms.Control
Klasse angeschaut.try { // ... ControlCollection controlCollection = (ControlCollection)this.Properties.GetObject(Control.PropControlsCollection); if (controlCollection != null) { for (int i = 0; i < controlCollection.Count; i++) { Control control = controlCollection[i]; control.parent = null; control.Dispose(); } this.Properties.SetObject(Control.PropControlsCollection, null); } // ... } finally { // ... }
Die gehen davon aus, dass keine Exception gefolgen kommt. Wobei sie selber eine Exception werfen, wenn Dipose aufgerufen wird, während gerade ein Handle erstellt wird:
if (this.GetState(262144)) { throw new InvalidOperationException(SR.GetString("ClosingWhileCreatingHandle", new object[] { "Dispose" })); }
Zumindest nehme ich an, dass der Status 262144 dies bedeutet, wenn ich mir die Nachricht der Exception anschaue
Grüssli
-
Dravere schrieb:
hustbaer schrieb:
Dravere schrieb:
@hustbaer,
Ehm ... ja. Hast du sowas jemals schon eingesetzt?Nicht genau in der Form. Wieso?
In der Form sehe ich es schon als etwas fragwürdig an. Dass ein Objekt mehrere Objekte verwaltet und diese dann bei Dispose freigibt, ist natürlich etwas anderes. Aber nur um z.B. die Verschachtelung zu verkleinern?
Ich finde das nicht fragwürdig. Warum genau findest du das fragwürdig, würde mich interessieren?
Die Klasse kann man ja auch für verschiedenste Dinge einsetzen, u.A. auch als Member.
Solche kleinen Hilfsklassen sind in C++ auch sehr üblich, k.A. warum das von der C# Community als "pfui" angesehen wird.
hustbaer schrieb:
Dispose Exceptions werfen zu lassen ist ca. eine so gute Idee wie in C++ einen Destruktor eine Exception werfen zu lassen.
hustbaer schrieb:
Wie gesagt sollte mMn. Dispose() niemals eine Exception werfen.
Damit bin ich absolut einverstanden. Allerdings halte ich es genauso für falsch, Exceptions einfach zu schlucken und nicht zu behandeln. Sowas kann böse ins Auge gehen.
hustbaer schrieb:
Wenn jetzt jemand fieserweise doch eine Exception aus Dispose() wirft, muss ich diese Gemeinheit nicht unbedingt an Clients meiner Klasse weitergeben.
Du probierst also eine Fehlentscheidung im Design mit einer weiteren Fehlentscheidung zu korrigieren, zumindest meiner Meinung nach. Diesen Fehler kann man meiner Meinung nach allerdings nicht korrigieren. Wenn jemand so eine Klasse verwendet, dann wird er wohl darüber informiert sein, dass dies passieren kann oder wird es dann bei der Benutzung mitbekommen. Du verschlimmerst den Designfehler nur noch, wenn du die Exception nicht weitergibst.
Hm.
Was macht denn C# wenn sowas in geschachtelten using() Blöcken auftritt? Bzw. was ist allgemein der C#-Weg um Fälle zu behandeln wo eine Exception während des Stack-Unwindings geworfen wird.
Ich meine mich zu erinnern irgendwo gelesen zu haben dass solche Exceptions in bestimmten Fällen einfach geschluckt werden. Kann aber auch sein dass es dabei um Java ging, evtl. auch um einen bestimmten Sonderfall. Weiss nimmer genau