ScopeGuards in C#?



  • Hi,
    ich suche eine Implementation von ScopeGuards mit C# - wie hier in C++ - bzw. den C#-Ansatz um ausnahmesicheren Code zu schreiben. Den Finally-Block kenne ich, verstehe aber nicht wie er mir da helfen kann. Schließlich will ich den Code dort nur im Fehlerfall ausführen.

    Gruß Hazzel



  • ScopeGuards im Sinne des Wortes würden in C# deswegen nicht funktionieren, weil das Object nicht bei Verlassen des Scopes deleted wird, sondern irgendwann von der GC zu einem beliebigen späteren Zeitpunkt.

    Vom Ansatz her brauchts eine Klasse "Guard" mit einem Eventhandler und 2 Funktionnen:
    ClearEvents() ;
    ExecuteEvents();

    Jedes Object das einen Rollback braucht falls eine Exception ausgelöst wird trägt seine Rollbackfunktion in die Eventliste ein. (->Delegate)

    Am Ende des try-blocks steht dann die ClearEvents-Funktion die alle vorher registrierten Events wieder löscht. (Wird ja nur erreicht wenn keine Exception ausgelöst wurde)

    Im finally-block wird die ExecuteEvents()-Funktion aufgerufen. Wenn ClearEvents erreicht wurde, passiert nix (leere Liste). Wurde dagegen eine Excpetion ausgelöst, stehen in der Eventliste die Rollback-Funktionen die nötig sind und werden ausgeführt.



  • Sowas hatte ich mir schon gedacht. Nur ich hätte gerne eine generische Implementierung dieser Guard Klasse wie im Link mit C++ beschrieben - soweit das überhaupt mit C# möglich ist. Ich kann ja schlecht tausende Delegate-Typen anlegen.



  • Denkbar wäre was mit IDisposeable und anonymen Delegaten. Ich spinne mir mal was zusammen (entsteht während ich hier schreibe):

    public delegate void ScopeGuardHandler();
    
        public class ScopeGuard : IDisposable
        {
            private ScopeGuardHandler m_handler;
            private bool m_dismissed;
            private bool m_disposed;
    
            public ScopeGuard(ScopeGuardHandler handler)
            {
                m_handler = handler;
                m_dismissed = false;
                m_disposed = false;
            }
    
            public void Dismiss()
            {
                m_dismissed = true;
            }
    
            public void Dispose(bool disposing)
            {
                if (!m_disposed) {
                    if (disposing) {
                        if (!m_dismissed) {
                            m_handler.Invoke();
                        }
                    }
                    m_handler = null;
                    m_disposed = true;
                }
            }
    
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
    
            ~ScopeGuard()
            {
                Dispose(false);
            }
        }
    

    Ein Anwendungsbeispiel:

    class Program
        {
            static void Main(string[] args)
            {
                List<string> names = new List<string>();
    
                try {
                    InsertName(names, "Sascha");
                } catch (Exception e) {
                    MessageBox.Show(e.Message);
                }
            }
    
            static void InsertName(List<string> names, string name)
            {
                names.Add(name);
                using (ScopeGuard guard = new ScopeGuard(delegate() { names.Remove(name); })) {
                    DbInsertName(name);
                    guard.Dismiss();
                }
            }
    
            static void DbInsertName(string name)
            {
                throw new Exception("might throw");
            }
        }
    

    Man erkennt im Debugger, dass innerhalb des Using-Blocks names einen Eintrag hat, innerhalb des Catch-Blocks aber keinen mehr.



  • Danke für die Antwort, anonyme Delegates sind das wonach ich gesucht hab.


Anmelden zum Antworten