Laden...

[Erledigt] Am Ende einer Methode bestimmten Code ausführen, egal wie die Methode verlassen wird

Erstellt von warpi vor 13 Jahren Letzter Beitrag vor 13 Jahren 4.601 Views
W
warpi Themenstarter:in
21 Beiträge seit 2010
vor 13 Jahren
[Erledigt] Am Ende einer Methode bestimmten Code ausführen, egal wie die Methode verlassen wird

Hi

Ich möchte Methoden meiner Klassen mit einem Custom Attribut markieren und diese dann, am besten zur Compilezeit, zumindest aber nicht zur Runtime um Code ergänzen.

Konkret bedeutet das dass ich vor und nach dem Aufruf dieser markierten Methode eine andere ausführen möchte.



[Machwas]
public void foobar()
{
// bliblablub
}

soll sozusagen zu



[Machwas]
public void foobar()
{
machwasAnfang();
// bliblablub
machwasEnde();
}

werden.

Etwas ähnliches hatte ich in Java per Annotations und einem Compiler-Plugin gemacht. Hierbei wurde der Code zur Compilezeit nach Annotationen durchsucht und entsprechend ergänzt.

Mein Problem ist jetzt das ich bisher nicht gefunden habe wie ich in den Compile-Vorgang eingreifen kann oder an irgendeinem anderen Punkt vor dem letztendlichen Build den Code noch modifizieren kann.

Ich möchte das ganze auf diese Weise lösen weil, grob gesagt, am Anfang der Methode
ein Flag gesetzt wird und danach wird er wieder zurück gesetzt.
Wird dabei ein return übersehen kann der Flag natürlich nicht zurück gesetzt werden und ist damit in einem nicht gewünschten Zustand.

Ich möchte das Fehlerpotential ganz gern minimieren und die Entwickler nicht dazu zwingen ihren Code entsprechend aufbauen zu müssen.

Über Vorschläge in welche Richtung ich weiter nachforschen kann wäre ich dankbar, wenn ihr konkrete Vorschläge habt umso mehr. Bisher habe ich nur Möglichkeiten per Reflection gefunden um Code zur Laufzeit zu ändern. Das würde aber unnötigen Overheat erzeugen.

Ich hoffe ihr könnt mit der Beschreibung etwas anfangen 😃

Achja, PostSharp ist leider ausgeschlossen - zu teuer 😃

mfg warpi

1.044 Beiträge seit 2008
vor 13 Jahren

Hallo warpi,

das nennt sich Post-Compiler, das kannst du in Visual Studio einstellen. Siehe auch: Code zur Laufzeit manipulieren - Beeinflussung des Codes durch Attribute. Ansonten kann ich dir PostSharp empfehlen.

zero_x

W
warpi Themenstarter:in
21 Beiträge seit 2010
vor 13 Jahren

Das ging ja schnell ^^

Danke, ich schau mir den Thread mal an.

Wie gesagt PostSharp ist leider ausgeschlossen, für diese Spielerei ist der Preis leider noch nicht zu verantworten.

mfg warpi

1.361 Beiträge seit 2007
vor 13 Jahren

Hi warpi,

Ich möchte das ganze auf diese Weise lösen weil, grob gesagt, am Anfang der Methode
ein Flag gesetzt wird und danach wird er wieder zurück gesetzt.
Wird dabei ein return übersehen kann der Flag natürlich nicht zurück gesetzt werden und ist damit in einem nicht gewünschten Zustand.

Für solch einen Anwendungsfall finde ich **using **ganz hilfreich. Entwirf eine kleine Helferklasse/Struktur, die IDisposable implementiert und dieses Flag-Setzen und Zurücksetzen übernimmt.

Dann musst du nur den Code in ein

using(new FlaggedContex())
{
//...
}

einpacken, und die Dispose()-Methode wird immer aufgerufen. Bei jedem return. Sogar bei einer Exception!

beste Grüße
zommi

W
warpi Themenstarter:in
21 Beiträge seit 2010
vor 13 Jahren

Für solch einen Anwendungsfall finde ich **using **ganz hilfreich.

Hm interresanter Ansatz, an sowas hatte ich gar nicht gedacht.

Danke für den Tipp, ich glaub mit der Lösung für mein Problem kann ich sehr gut leben 😃

mfg warpi

F
10.010 Beiträge seit 2004
vor 13 Jahren

Schau dir lieber Codecontracts an.

1.044 Beiträge seit 2008
vor 13 Jahren

Hallo FZelle,

soweit ich weiß, ist es nicht mit CC möglich. Sinn und Zwekc von CC ist ein anderer. Warum meinst du, dass CC an der Stelle weiterhelfen soll?

zero_x

F
10.010 Beiträge seit 2004
vor 13 Jahren

CC ist dafür gedacht Ein und Ausgangswerte von Funktionen oder deren Umfeld zu überprüfen.
Das kann zur Compilezeit oder zur Laufzeit geschehen.

Man kann sogar Interfaces mit CC belegen, was zur folge hat, das überschriebene Funktionen automatisch getestet werden.

Code Contracts By Example

Denn eigentlich sollte nicht die Vergesslichkeit anderer Entwickler kaschiert sondern aufgedeckt werden.

1.361 Beiträge seit 2007
vor 13 Jahren

Hi,

CC haben natürlich hier auch ihre Berechtigung, aber ich finde, das geht in die falsche Richtung. Denn
*entweder lässt man die Klassen so bestehen wie sie sind und zwingt mittels CC den Programmierer dazu, beim Eintritt in die Property ein Flag zu setzen, und bei jedem Austritt dieses wieder zu löschen, wodurch er jedes return, jedes throw noch mit dieser zusätzlichen Anweisung spicken muss. Aber auch CC allein ändern nichts an der Tatsache, dass das Protokoll einfach ein schlechtes ist - sie zwingen mich nur, dieses ungünstige Protokoll einzuhalten!

*oder aber man ändert etwas am Protokoll und sagt, dass a) die Properties annotiert werden müssen, sodass ein IL-Weaver oder C#Code-Weaver noch die passenden Instruktionen automatisiert einbettet, oder b) verwendet die using-Variante. In beiden Fällen vereinfacht sich aber das einzuhaltene Protokoll drastisch, sodass ein Vergessen im Grunde schon durch die Simplizität des Protokolls ausgeschlossen ist.
Man könnte natürlich trotzdem noch CodeContracts verwenden um auch die Einhaltung dieses Protokolls zu erzwingen.
Aber um dieses Property mit passenden CC-Post/Prä-Conditions zu versehen, benötigt man in etwa genau so viel Schritte/Aufwand/Code um das Protokoll selbst direkt umzusetzen. Hier wäre also CC höchstens eine doppelte Absicherung. Beispielsweise dafür, dass niemand den Code wieder falsch verändert. Damit spielt der Nutzen von CC an dieser Stelle aber wieder unter die Kategorie von ganz schnöden Tests und hilft eigentlich nicht, die von warpi beschriebene Situation grundlegend zu verbessern.

Denn eigentlich sollte nicht die Vergesslichkeit anderer Entwickler kaschiert sondern aufgedeckt werden.

Und ich sage eben:
Denn eigentlich sollte nicht ein schlechtes Protokoll aufgrund seiner Komplexität und Fragilität durch massiven Einsatz von Tests und Contracts überprüft und erzwungen werden, sondern lieber das Protokoll vereinfacht werden. 😉

beste Grüße
zommi

1.361 Beiträge seit 2007
vor 13 Jahren

Hi warpi,

man könnte auch den "normalen", rein objektorientierten Ansatz wählen, ohne Sprachen-Features wie Annotations, Attribute, Using:

Zwinge die Implementierer nicht, das Protokoll einzuhalten, implementiere in einer geeigneten Oberklasse die Property einfach selbst als Template-Method. Mit korrektem Pro- und Epilog und rufe zwischendrin die protected, überschreibbare, TeilMethode/Property auf, die überschrieben werden muss - und zwar einzig allein mit der reinen Funktionalität.

Vielleicht geht das ja bei deiner Architektur, das wäre wohl der schönste Ansatz. (Und falls es nicht geht, änder was an deiner Architektur! 😉 Liegt dann bestimmt an irgendwelchen Code Smells 😉

beste Grüße
zommi

F
10.010 Beiträge seit 2004
vor 13 Jahren

Zommi:
Wir wissen aber nicht warum dieses Protokoll so ist, wie es ist.

W
warpi Themenstarter:in
21 Beiträge seit 2010
vor 13 Jahren

Vll doch eine konkretere Beschreibung meines Problems:
_
Ich habe eine Form das u.a. ein TabControl enthält. Ein Tab hält wiederrum ein UserControl mit verschiedensten Controls.
Die Form ist also nur der Rahmen, der UserControls verwaltet - grob gesagt.

Mein Problem war nun das ich einen WaitCursor bei bestimmten Aktionen anzeigen muss.
Die bisherige Lösung sieht so aus das ich in einer Singleton Klasse "WaitCursor" eine Liste von Controls habe die einen WaitCursor darstellen wollen. (Das hier Refrenzen auf Controls gespeichert werden ist atm nur zum Nachverfolgen gedacht, ein Counter würde den gleichen Zweck erfüllen).
Bei einer Methode wird nun am Anfang "RegisterWaitCursor(this)" aufgerufen und am ende "UnRegisterWaitCursor(this)".
Wenn alle Fälle, in denen Die Methode verlassen wird, bedacht werden funktioniert das auch gut. Es ist allerdings unangenehm zu benutzen und Fehleranfällig falls ein Fall übersehen wird.

Für die using Variante müsste ich zwar die Struktur umbauen, allerdings wäre die lesbarkeit innerhalb der modifizierten Methoden gut und die Fehleranfälligkeit praktisch eleminiert (falls ich jetzt nichts übersehe). Von daher -> Aufwand gerechtfertigt ^^_

Ich denke Template-Methods kann ich nicht verwenden da ich möglichst wenig Änderungen am bisher bestehenden Code erzwingen möchte, und nicht sicher bin ob so etwas mit den Control Events zu vereinen ist?

Die Methode über using wäre bisher zumindest die übersichtlichste und zweckmäßigste.

Mir hat bisher die Zeit gefehlt mich tiefer in CCs einzuarbeiten aber auf den ersten Blick schienen sie mir für mein konkretes Problem weniger geeignet, da ich den Programmierer zu nichts zwingen will sondern ihm Arbeit abnehmen.

Allerdings stehen CCs definitiv auf meiner Liste der Dinge die ich mir näher anschauen werde.

Gruß warpi

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo warpi,

fast genauso elegant wie using, auf jeden Fall genauso sicher und in deinem konkreten Fall verständlicher, wäre ein try/finally, also

public void foobar()
{
   try {
      RegisterWaitCursor(this);
      // bliblablub
   } finally {
      UnRegisterWaitCursor(this)
   }
}

herbivore

1.130 Beiträge seit 2007
vor 13 Jahren

Beinahe hätte ich gefragt: Funktioniert das auch mit returns im try?
Habs dann aber einfach schnell ausprobiert und ja, finally wird immer aufgerufen
👍 wusste ich noch garned.

Projekte:Jade, HttpSaver
Zum Rechtschreiben gibts doch schon die Politiker. Aber die bauen auch nur mist!

U
1.688 Beiträge seit 2007
vor 13 Jahren

Wie gesagt PostSharp ist leider ausgeschlossen, für diese Spielerei ist der Preis leider noch nicht zu verantworten.

Die Community-Edition sollte reichen und dürfte sogar kommerziell verwendet werden: Compare PostSharp Editions

2.891 Beiträge seit 2004
vor 13 Jahren

Zum Setzen des WaitCursors benutze ich ein IDisposable (nicht threadsichere Lösung):


public class WaitCursorChanger : IDisposable
{
   private readonly Cursor rawCursor;

   public WaitCursorChanger()
   {
      this.rawCursor = Cursor.Current;
      if (Cursor.Current!=Cursors.WaitCursor)
         Cursor.Current = Cursors.WaitCursor;
   }

   public void Dispose()
   {
      if (Cursor.Current!=this.rawCursor)
         Cursor.Current = this.rawCursor;
   }
}

Die Verwendung von IDisposable ist da von Vorteil, weil man auch beliebig schachteln kann:


public void Foo()
{
   using (new WaitCursorChanger())
   {
      // ...
      this.Bar();
   }
}

public void Bar
using (new WaitCursorChanger())
   {
      // ...
   }

Gruß,
dN!3L

W
warpi Themenstarter:in
21 Beiträge seit 2010
vor 13 Jahren

Die Community-Edition sollte reichen und dürfte sogar kommerziell verwendet werden:
>

Das man die Community Version auch im kommerziellen Bereich nutzen darf habe ich wohl übersehen. Das dürfte definitiv nochmal einen Blick wert sein, wenn auch nicht unbedingt für dieses Problem.

Ich habe mich jetzt erstmal für die using Variante entschieden.

Das ist dabei jetzt erstmal heraus gekommen:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Util
{
    public class WaitCursorChangedArgs
    {
        bool m_ShowWaitCursor;
        public bool ShowWaitCursor
        {
            get { return m_ShowWaitCursor; }
        }

        public WaitCursorChangedArgs(bool ShowWaitCursor)
        {
            this.m_ShowWaitCursor = ShowWaitCursor;
        }
    }

    
    public static class WaitCursor
    {
        /// <summary>
        /// Hilfsklasse
        /// </summary>
        public class WaitAction : IDisposable
        {
            private object sender;
            public WaitAction(object sender)
            {
                this.sender = sender;
                WaitCursor.RegisterWaitAction();
            }
            public void Dispose()
            {
                WaitCursor.UnRegisterWaitAction();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        private static volatile int PendingWaitActions = 0;
        /// <summary>
        /// TimeStamp von letzter WaitAction
        /// </summary>
        private static DateTime lastAction = DateTime.Now;

        /// <summary>
        /// Erstellt eine WaitAction
        /// </summary>
        public static WaitAction Wait
        {
            get
            {
#if DEBUG
                System.Diagnostics.StackTrace s = new System.Diagnostics.StackTrace(System.Threading.Thread.CurrentThread, true);
                return new WaitAction(s.GetFrame(1).GetMethod());
#else
                return new WaitAction(null);
#endif
            }
        }

        /// <summary>
        /// Registriert eine neue Aktion in der Warteschlange
        /// </summary>
        /// <param name="sender">Das aufrufende Objekt</param>
        /// <returns>Gibt an wie viele Aktionen gerade Arbeiten</returns>
        private static int RegisterWaitAction()
        {
            // Notbremse um etwaige Fehler zu korrigieren.
            if ((DateTime.Now - lastAction).Seconds > 2) ResetPendingWaitAction();

            try
            {
                PendingWaitActions++;
                lastAction = DateTime.Now;
                if (!Application.UseWaitCursor) WaitCursorChanged.Invoke(null, new WaitCursorChangedArgs(true));
                return PendingWaitActions;
            }
            catch
            {
                return 0;
            }

        }

        /// <summary>
        /// Löscht eine Aktion aus der Liste der Arbeitenden Aktionen
        /// </summary>
        /// <param name="sender"></param>
        private static void UnRegisterWaitAction()
        {
            if (PendingWaitActions > 0)
            {
                PendingWaitActions--;
            }
            if (PendingWaitActions <= 0)
            {
                if (Application.UseWaitCursor) WaitCursorChanged.Invoke(null, new WaitCursorChangedArgs(false));
            }
        }

        /// <summary>
        /// Setzt die Liste der Arbeitenden Aktionen zurück. Notbremse :)
        /// </summary>
        public static void ResetPendingWaitAction()
        {
            PendingWaitActions = 0;
            foreach (Form form in Application.OpenForms)
            {
                form.Cursor = Cursors.Default;
            }
            System.Windows.Forms.Application.UseWaitCursor = false;
        }

        /// <summary>
        /// Ereigniss das dem Hauptfenster eine Änderung am Cursorstatus mitteilt.
        /// Wird benötigt da WaitCursor in einem anderen Projekt liegt.
        /// </summary>
        public static OnWaitCursorChanged WaitCursorChanged;
        public delegate void OnWaitCursorChanged(object sender, WaitCursorChangedArgs args);
        
    }
}

Aufruf:


public void Foo()
{
   using(WaitCursor.Wait)
   {
      // Irgendwas
   }
}

Im Hauptfenster sorgt diese Methode dafür das mein WaitCursor korrekt angezeigt wird. Ob das der optimale Weg ist bin ich mir nicht sicher, allerdings funktioniert es ^^



public Bar()
{
...
WaitCursor.WaitCursorChanged += new WaitCursor.OnWaitCursorChanged(WaitCursorChanged);
...
}

public void WaitCursorChanged(object sender, WaitCursorChangedArgs e)
        {
            this.Invoke(new OnWaitCursorChanged(SetWaitCursor), e.ShowWaitCursor);
        }

public void SetWaitCursor(bool ShowWaitCursor)
        {
            Application.UseWaitCursor = ShowWaitCursor;
            if (ShowWaitCursor)
                this.Cursor = Cursors.WaitCursor;
            else
                this.Cursor = Cursors.Default;
        }

danke für die Ratschläge 😃
Sollte ich nochmal eine andere Version mit PostSharp machen werde ich die natürlich noch posten. Ich denke aber das die oben zunächst simpel genug anzuwenden ist und flexibel genug einzubinden.

mfg warpi