Alternativen zu geschachtelten Using Statements?



  • 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");
            }
        }
    }
    

    @Dravere:

    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".


  • Administrator

    µ 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üdung

    Achso, 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 🙂


  • Administrator

    hustbaer schrieb:

    Ich finde das nicht fragwürdig. Warum genau findest du das fragwürdig, würde mich interessieren?

    Weil ich der Meinung bin, dass dies eine Lösung für schlechtes Design ist. Wie beim Beispiel von Q. Man kann diese Verschachtelung wunderbar in einzelne Funktionen auseinander nehmen. Statt das jemand seinen Code in kleine und lesbare Funktionen unterteilt, würde er dann so ein Hilfskonstrukt verwenden, was nicht hilfreich ist.

    hustbaer schrieb:

    Die Klasse kann man ja auch für verschiedenste Dinge einsetzen, u.A. auch als Member.

    Wie gesagt, damit hätte ich keine Probleme.

    hustbaer schrieb:

    Solche kleinen Hilfsklassen sind in C++ auch sehr üblich, k.A. warum das von der C# Community als "pfui" angesehen wird.

    Lese ich meistens nur von gewissen Exponenten, dass dies "pfui" sein soll. Mir käme da zum Beispiel Eric Lippert in den Sinn, welcher zum Beispiel dagegen ist, dass man das Dispose-Pattern für RAII in C# verwendet. Bzw. er ist dann auch dagegen, dass man RAII für Dinge verwendet, was nichts mit Ressourcen zu tun hat, wobei er Ressourcen sehr fragwürdig definiert. Einfach mal auf Stackoverflow nach Eric Lippert, RAII und C# suchen 🙂

    hustbaer schrieb:

    Was macht denn C# wenn sowas in geschachtelten using() Blöcken auftritt?

    Du meinst also, wenn Dispose aufgerufen wird wegen einer Exception und darin eine weitere Exception geworfen wird? Das using Statement verwendet dazu ja finally Blöcke. Wenn man in finally eine weitere Exception wirft, dann wird die aktuelle verworfen und die neue geworfen.

    Kann man in "8.9.5 The throw statement" der Sprachedefinition nachlesen:

    In the current function member, each try statement that encloses the throw point is examined. For each statement S, starting with the innermost try statement and ending with the outermost try statement, the following steps are evaluated:
    - If the try block of S encloses the throw point and if S has one or more catch clauses, the catch clauses are examined in order of appearance to locate a suitable handler for the exception. The first catch clause that specifies the exception type or a base type of the exception type is considered a match. A general catch clause (§8.10) is considered a match for any exception type. If a matching catch clause is located, the exception propagation is completed by transferring control to the block of that catch clause.
    - Otherwise, if the try block or a catch block of S encloses the throw point and if S has a finally block, control is transferred to the finally block. If the finally block throws another exception, processing of the current exception is terminated. Otherwise, when control reaches the end point of the finally block, processing of the current exception is continued.

    hustbaer schrieb:

    Bzw. was ist allgemein der C#-Weg um Fälle zu behandeln wo eine Exception während des Stack-Unwindings geworfen wird.

    Da bin ich überfragt. Bei den Destruktoren wird falls vorhanden der Basis-Destruktor aufgerufen und danach wird die Exception verworfen.

    In "16.3 How Exceptions are handled" kann man dazu nachlesen:

    Exceptions that occur during destructor execution are worth special mention. If an exception occurs during destructor execution, and that exception is not caught, then the execution of that destructor is terminated and the destructor of the base class (if any) is called. If there is no base class (as in the case of the object type) or if there is no base class destructor, then the exception is discarded.

    Grüssli



  • Du meinst also, wenn Dispose aufgerufen wird wegen einer Exception und darin eine weitere Exception geworfen wird?

    Ja, genau das meine ich.

    Wenn man in finally eine weitere Exception wirft, dann wird die aktuelle verworfen und die neue geworfen.

    Finde ich ehrlich gesagt nicht weniger fragwürdig als "meine" Variante 🙂

    Bei den Destruktoren wird falls vorhanden der Basis-Destruktor aufgerufen und danach wird die Exception verworfen.

    Ich muss mich erst noch daran gewöhnen dass C# das Destruktor nennt. 🙂
    Und auch das finde ich nicht wesentlich weniger fragwürdig als "meine" Variante.

    Klar kann man argumentieren der Finalizer-Thread hat ja keine Chance die Exception irgendwem zu geben der sich dann darüm kümmert, da er keinen "Kontext" mehr hat über den er ermitteln könnte wer zuständig ist.

    Nur ... wenn man schon die Schiene fährt "Exceptions gehören behandelt", dann muss man an dem Punkt das Programm abbrechen.
    Bzw. genau so wenn während des Unwinding eine neue Exception fliegt.

    Das .NET Framework tut beides nicht. Ich denke ich weiss auch wieso. Ich denke aber auch dass ich mir keine Freunde mache wenn ich meine Vermutung hier kundtue. 😉



  • Microsoft schreibt

    MSDN schrieb:

    Eine Finalize-Methode sollte keine Ausnahmen auslösen, da die Anwendung sie nicht behandeln kann und sie das Beenden der Anwendung verursachen können.

    http://msdn.microsoft.com/de-de/library/0s71x931.aspx

    Ich kenn das so das man in C# immer von Finalizern spricht. Diese werden in der Destruktor Syntax geschrieben.

    MSDN schrieb:

    Um die Finalize-Methode in C# zu implementieren, müssen Sie die Destruktorsyntax verwenden.

    http://stackoverflow.com/questions/1076965/in-c-sharp-what-is-the-difference-between-a-destructor-and-a-finalize-method-in

    stackoverflow schrieb:

    A destructor in C# overrides System.Object.Finalize

    stackoverflow schrieb:

    C# really doesn't have a "true" destructor. The syntax resembles a C++ destructor, but it really is a finalizer

    http://unboxedsolutions.com/sean/archive/2005/04/10/480.aspx

    What we used to call destructors in C# are now being called finalizers, and the Dispose method is now being called a destructor.

    http://www.c-sharpcorner.com/UploadFile/chandrahundigam/UnderstandingDestructors11192005021208AM/UnderstandingDestructors.aspx

    So our conclusion from this is Finalize is another name for destructors in C#.



  • @David W
    Das .NET Framework hat was was sich "Finalizer" nennt. Das ist (mehr oder weniger) ne ganz normale Methode, und die kann halt aufgerufen werden, und der Finalizer-Thread macht das auch automatisch.

    Und C# hat was was sich "Destruktor" nennt (ich finde es auch doof, aber in der offiziellen Doku von MS wird das Wort leider verwendet).

    Der C# Destruktor implementiert dabei den Finalizer. Und zwar mit "Chaining", d.h. es wird automatisch auch der Finalizer der Basisklasse aufgerufen.

    Daher macht die Unterscheidung der beiden Begriffe (zumindest manchmal) Sinn. Wobei ich wie gesagt den Begriff "Destruktor" für unglücklich gewählt halte, eben weil man dabei normalerweise an C++ und deterministische Finalisierung denkt.


Anmelden zum Antworten