Laden...

[Artikel] Attribute zur Prüfung von Properties verwenden

Erstellt von herbivore vor 17 Jahren Letzter Beitrag vor 10 Jahren 111.817 Views
herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 17 Jahren
[Artikel] Attribute zur Prüfung von Properties verwenden

Hallo Community,

oft besteht der Bedarf, in den Settern von Properties, den übergebenen Wert (value) zu prüfen, bevor er in einem Feld gespeichert wird. Meist erfolgt dies durch händisches Abfragen und Werfen von Exceptions.


public String Street
{
   ...
   set {
      If (value.Length > 20) { // <= Implementierung einer Prüfung
         throw new Exception ("Zu lang");
      }
      _strStreet = value;
    }
}

Dies hat mehrere Nachteile. Aus Sicht des Implementierer der Property wird der Code - insbesondere wenn mehrere Prüfungen erforderlich sind - unnötig aufgebläht und gleichzeitig redundant. Aus Sicht des Benutzers der Property verschwinden die Prüfungen in der Implementierung und sind nach außen nicht bekannt.

Diese Nachteile lassen sich mit einem Schlag beheben, wenn man durch Attribute angibt, welche Prüfungen erfolgen (sollen), die Prüfungen also quasi nur noch deklariert.


[MaxStringLength (20)] // <= Deklaration einer Prüfung
public String Street
{
   ...
   set {
      AttributeChecker <String>.Check (value);
      _strStreet = value;
    }
}

Damit ist nach außen nicht nur automatisch dokumentiert, dass die maximale Stringlänge 20 ist, sondern ein Benutzer der Klasse kann diese Information sogar zu Laufzeit abfragen, z.B. um die Größe bzw. die maximale Länge einer TextBox entsprechend festzulegen.

Bei diesem Ansatz gab es zwei Herausforderungen zu bewältigen. (1) In der Attribut-Klasse gibt es keine direkte Unterstützung, generisch die Attribute der aktuellen Property zu erhalten. Auch für die umgekehrte Richtung gibt es keine direkte Unterstützung. (2) Ein Attribut-Objekt weiß also nicht, zu welcher Property es gehört. Dank der allgemeinen Reflection konnten aber beide Probleme gelöst werden.

Der folgende Code zeigt, wie das Ganze funktioniert. Um das Ausprobieren zu erleichtern, habe ich alle benötigten Klassen und Interfaces in eine Anwendung gepackt. Da die Interfaces und Hilfsklassen nur einmal vorhanden sein müssen und dann in allen (kommenden) Modellklassen benutzt und wiederverwendet werden können, würde man den Code in einer "echten" Anwendung natürlich in mehrere Dateien aufteilen.


using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;

//*****************************************************************************
// Interface
public interface CheckableAttribute
{
}

//*****************************************************************************
// Interface
public interface CheckableAttribute <T> : CheckableAttribute
{
   //==========================================================================
   void Check (PropertyInfo pi, T tValue);
}

//*****************************************************************************
// Technische Hilfsklasse
public static class AttributeChecker <T>
{
   //==========================================================================
   public static void Check (T tValue)
   {
      Helper.SayHuhu ();

      MethodBase   mb;
      String       strProperty;
      Type         typDeclaring;
      PropertyInfo pi;

      //-----------------------------------------------------------------------
      // Ermitteln von welcher Property welcher Klasse wir aufgerufen wurden
      //-----------------------------------------------------------------------
      mb = new StackFrame (1).GetMethod ();

      if (!mb.IsSpecialName) {
         throw new Exception ("PropertyCheckException: "
                            + "Check wurde nicht direkt von einer "
                            + "Property aus aufgerufen");
      }

      strProperty = mb.Name.Substring (4);
      typDeclaring = mb.DeclaringType;
      pi = typDeclaring.GetProperty (strProperty,
                                     (mb.IsStatic
                                      ? BindingFlags.Static
                                      : BindingFlags.Instance)
                                   | (mb.IsPublic
                                      ? BindingFlags.Public
                                      : BindingFlags.NonPublic));

      if (pi == null) {
         throw new Exception ("PropertyCheckException: "
                            + "Check wurde nicht direkt von einer "
                            + "Property aus aufgerufen");
      }

      //-----------------------------------------------------------------------
      // (1) Ermitteln der (prüfbaren) Attribute zur der Property
      // Anm. Hier wird absichtlich das allgemeine CheckableAttribute
      //      verwendet
      //-----------------------------------------------------------------------
      Object [] achkattr = pi.GetCustomAttributes (
                              typeof (CheckableAttribute),
                              true
                           );

      //-----------------------------------------------------------------------
      // Eigentliche Prüfung
      // Anm. Hier wird absichtlich das spezifische CheckableAttribute <T>
      //      verwendet. Dadurch gibt eine Exception, wenn ein
      //      CheckableAttribute vom falschen Typ verwendet wird.
      //-----------------------------------------------------------------------
      foreach (CheckableAttribute <T> chkattr in achkattr) {
         //--------------------------------------------------------------------
         // Durchführen der eigentlichen Prüfung und dabei
         // (2) mitteilen, zu welcher Property das Attribut gehört
         //--------------------------------------------------------------------
         chkattr.Check (pi, tValue);
      }
   }
}

//*****************************************************************************
// Technische Hilfsklasse
public static class Helper
{
   //==========================================================================
   public static void SayHuhu ()
   {
      // besser Debug.WriteLine
      Console.WriteLine ("==> "
                       + new StackFrame (1).GetMethod ().DeclaringType.Name
                       + "."
                       + new StackFrame (1).GetMethod ().Name);
   }
}

//*****************************************************************************
// Hilfsklasse
[AttributeUsage (AttributeTargets.Property)]
public class MaxStringLengthAttribute : Attribute, CheckableAttribute <String>
{
   //--------------------------------------------------------------------------
   private int _iMaxStringLength;

   //==========================================================================
   public MaxStringLengthAttribute ()
   {
      Helper.SayHuhu ();
      _iMaxStringLength = -1;
   }

   //==========================================================================
   public MaxStringLengthAttribute (int iMaxStringLength)
   {
      Helper.SayHuhu ();
      _iMaxStringLength = iMaxStringLength;
   }

   //==========================================================================
   public void Check (PropertyInfo pi, String strValue)
   {
      Helper.SayHuhu ();

      //-----------------------------------------------------------------------
      // Nötigenfalls Ermitteln der Feldlänge
      //-----------------------------------------------------------------------
      if (_iMaxStringLength < 0) {
         // besser Debug.WriteLine
         Console.WriteLine ("Simulierter Datenbankzugriff, "
                          + "um die Feldlänge zu ermitteln");
         if (pi.Name == "Street") {
            // besser Debug.WriteLine
            Console.WriteLine ("Simulierter Datenbankzugriff erfolgreich");
            _iMaxStringLength = 20;
         }
      }

      //-----------------------------------------------------------------------
      // Eigentliche Prüfung
      //-----------------------------------------------------------------------
      if (strValue.Length > _iMaxStringLength) {
         throw new Exception ("MaxStringLengthException: "
                            + pi.DeclaringType.Name
                            + "."
                            + pi.Name
                            + @": """
                            + strValue
                            + @""" ist "
                            + strValue.Length
                            + " Zeichen lang. Erlaubt sind aber nur "
                            + _iMaxStringLength
                            + " Zeichen.");
      }
   }
}

//*****************************************************************************
// Hilfsklasse
[AttributeUsage (AttributeTargets.Property)]
public class StringFormatAttribute : Attribute, CheckableAttribute <String>
{
   //--------------------------------------------------------------------------
   private String _strPattern;

   //==========================================================================
   public StringFormatAttribute (String strPattern)
   {
      Helper.SayHuhu ();
      _strPattern = strPattern;
   }

   //==========================================================================
   public void Check (PropertyInfo pi, String strValue)
   {
      Helper.SayHuhu ();
      if (!Regex.IsMatch (strValue, "^" + _strPattern + "$")) {
         throw new Exception ("StringFormatException: "
                            + pi.DeclaringType.Name
                            + "."
                            + pi.Name
                            + @": """
                            + strValue
                            + @""" entsprich nicht dem Muster ""^"
                            + _strPattern
                            + @"$"".");
      }
   }
}

//*****************************************************************************
// Modellklasse
public class Address
{
   //--------------------------------------------------------------------------
   private String _strStreet;

   //==========================================================================
   public Address ()
   {
      Helper.SayHuhu ();
   }

   //==========================================================================
   [MaxStringLength ()]
   [StringFormat ("[a-zA-ZäöüÄÖÜß ]*")]
   public String Street
   {
      get {
         Helper.SayHuhu ();
         return _strStreet;
      }

      [MethodImpl (MethodImplOptions.NoInlining)]
      set {
         Helper.SayHuhu ();
         AttributeChecker <String>.Check (value);
         _strStreet = value;
       }
   }
}

//*****************************************************************************
// Hauptklasse
static class App
{
   //==========================================================================
   public static void Main (string [] astrArg)
   {
      Helper.SayHuhu ();

      //-----------------------------------------------------------------------
      Address addr = new Address ();

      //-----------------------------------------------------------------------
      Console.WriteLine ("");
      Console.WriteLine ("Setze korrekten Straßennamen");
      try {
         addr.Street = "Kurz genug";
      }
      catch (Exception exc) {
         Console.WriteLine (exc.Message);
      }

      //-----------------------------------------------------------------------
      Console.WriteLine ("");
      Console.WriteLine ("Setze zu langen Straßennamen");
      try {
         addr.Street = "Dieser Straßenname ist zu lang";

      }
      catch (Exception exc) {
         Console.WriteLine (exc.Message);
      }

      //-----------------------------------------------------------------------
      Console.WriteLine ("");
      Console.WriteLine ("Setze Straßennamen mit ungültigen Zeichen");
      try {
         addr.Street = "!õ$%&/(";
      }
      catch (Exception exc) {
         Console.WriteLine (exc.Message);
      }
   }
}

Um die Prüfungen bezüglich des Typs der Property (String, int, ...) generisch, aber gleichzeitig typsicher zu machen, habe ich Generics aus .NET 2.0 verwendet. Der Code ließe sich aber auch so umschreiben, dass er unter .NET 1.1 verwendbar wäre. Dazu müssten - grob gesprochen - alle Teile in spitzen Klammern entfernt und der Typ T durch Object ersetzt werden. In der Folge werden dann einige weitere Änderungen wie z.B. Casts fällig. Außerdem muss an drei Stellen static vor class entfernt werden.

Über Anmerkungen und Verbesserungsvorschläge würde ich mich freuen. [EDIT]Diese sind erfreulicherweise in großer Zahl erfolgt. Außerdem habe ich selber im Verlaufe des Threads einige Weiterentwicklungen vorgestellt. So finden sich weiter unten nicht nur Verbesserungen hinsichtlich der Ermittlung der aktuellen Property, sondern sogar eine Variante, die auf AOP/PostSharp basiert.[/EDIT]

herbivore

PS: gfoidl hat einige Ideen aus diesem Artikel aufgegriffen. Er hat ein eigenes Validierungssystem geschaffen und in ValidationRules und Attribute zur Validierung veröffentlicht. Wo meinen Ansatz auf dem Werfen von Exceptions basiert, setzt er auf das IDataErrorInfo-Interface, weshalb sich sein Ansatz viel besser für DataBinding-Szenarien eignet. Außerdem nutzt sein Ansatz zwar standardmäßig Attribute, um die Regeln anzugeben, ist aber nicht darauf beschränkt. Bei ihm können die Regeln von beliebigen Orten stammen, z.B. aus der Konfiguration oder aus einer Datenbank. Außerdem ist in seinem Ansatz die Lokalisierung der Fehlermeldungen bereits implementiert. Mit anderen Worten: Mein Artikel beschreibt ein Konzept, gfoidl bietet eine fertige Komponente.

PPS: Weiter unten werden weitere Möglichkeiten der Ermittlung des Namens der Property beschrieben, den man braucht, um auf die Attribute zuzugreifen. Hervorzuheben sind die Möglichkeit per MethodBase.GetCurrentMethod ().Name und die Möglichkeit per CallerMemberName (ab .NET 4.5 / C# 5.0).

Siehe auch
[Artikel] Attributbasierte Programmierung
ValidationRules und Attribute zur Validierung

S
8.746 Beiträge seit 2005
vor 17 Jahren

Schöne Lösung. Was für CodeProjekt! 😉

Hast du mal eine Performanceanalyse gemacht? Um welchen Faktor ist der Spass teurer?

Das wird dir übrigens in dem Zusammenhang sehr gefallen:

Including Assertions in .NET Assemblies

Leider akademisch. Gibt nur den Artikel.

Etwas anders aber in die Richtung:

http://www.resolvecorp.com/Products.aspx

2.082 Beiträge seit 2005
vor 17 Jahren

Hallo herbivore,

wirklich gut was du da verzapft (^^) hast.

Großes Lob! 👍

Es ist toll jemand zu sein, der nichts von der persönlichen Meinung Anderer hält. - frisch-live.de

X
2.051 Beiträge seit 2004
vor 17 Jahren

dem kann ich auch nix mehr hinzufügen. 😉

1.985 Beiträge seit 2004
vor 17 Jahren

Hallo herbivore,

sehr sehr schöne Lösung. Einfach nur gut gemacht! Eine Performanceanalyse, wie sie svenson schon vorgeschlagen hat, würde mich auch noch interessieren.

Gruß,
Fabian

"Eine wirklich gute Idee erkennt man daran, dass ihre Verwirklichung von vornherein ausgeschlossen erscheint." (Albert Einstein)

Gefangen im magischen Viereck zwischen studieren, schreiben, lehren und Ideen umsetzen…

Blog: www.fabiandeitelhoff.de

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 17 Jahren

Hallo zusammen,

erstmal vielen Dank für die vielen freundlichen Antworten.

Hast du mal eine Performanceanalyse gemacht? Um welchen Faktor ist der Spass teurer?

Frag doch nicht sowas 🙂

Die Performance ist natürlich grausam. Ich habe mal die beiden Varianten verglichen, die ich am Anfang meines Artikels gegenübergestellt habe (max. Länge 20). Alle Consolen-Ausgaben haben habe ich entfernt. Ich habe nur den Fall getestet, dass die Prüfung bestanden wird. Der Faktor ist 4000, was ich auch nicht besonders verwunderlich finde. Die Verwendung der Property ohne Attribute kostet ja nur den Methodenaufruf des Setters, eine Abfrage und eine Zuweisung, also quasi nichts. Bei Verwendung der Property mit Attributen werden etliche Objekte erzeugt und alleine jede einzelne Objekterzeugung dürfte um einiges teurer sein, als der gesamte Zugriff auf eine attributlose Property. Außerdem sind es nicht irgendwelche Objekte, die da erzeugt werden, sondern die teureren Reflection-Objekte.

Allerdings ist 4000 mal Nichts trotzdem nicht wirklich viel. Auf meinem Rechner kann ich pro Sekunde ca. zwanzigtausend Properties setzen. Das würde in einer Datenbankanwendung vermutlich nicht der Flaschenhals sein.

Ca. 2/3 der Performance gehen übrigens auf das Konto der Ermittlung von strProperty und typDeclaring und dabei wiederum macht GetStackFrame den Löwenanteil aus.

Leider bin ich bei der Performance-Analyse auf eine weitere Herausforderung gestoßen. Im Moment funktioniert mein Code nur im Debug-Modus zuverlässig, da im Release-Modus scheinbar die Aufrufe des Setters wegoptimiert werden (inline). Es gibt dann also keinen Stackframe für den Aufruf des Setters und entsprechend liefert GetStackframe (1) den Stackframe der Methode, die den Setter aufruft. Damit schlägt aber die Ermittlung von strProperty und typDeclaring fehl. Die Prüfung kann nicht durchgeführt werden. Leider werde ich mich vermutlich erst in zwei Wochen diesem Thema wieder intensiv widmen können.

herbivore

564 Beiträge seit 2006
vor 17 Jahren

Hallo herbivore!

👍

der Marcel

:] 😄Der größte Fehler eines modernen Computers sitzt meist davor 😁 :]

1.985 Beiträge seit 2004
vor 17 Jahren

Hallo herbivore,

Faktor 4000 ist natürlich schon etwas. Wobei, wie Du schon sagtest, wird das nicht der Flaschenhals sein.
Danke für Deine Analyse.

Die zweite, auch im Release-Modus funktionierende Variante, würde mich auch interessieren 🙂.

Gruß,
Fabian

"Eine wirklich gute Idee erkennt man daran, dass ihre Verwirklichung von vornherein ausgeschlossen erscheint." (Albert Einstein)

Gefangen im magischen Viereck zwischen studieren, schreiben, lehren und Ideen umsetzen…

Blog: www.fabiandeitelhoff.de

X
2.051 Beiträge seit 2004
vor 17 Jahren

Original von Fabian
Die zweite, auch im Release-Modus funktionierende Variante, würde mich auch interessieren 🙂.

*g* mich auch.

als Abhilfe könnte man der Check-Methode den Namen der Property und Type des aktuellen Objektes mitteilen

      
set 
{
...
         AttributeChecker <String>.Check (value, this.GetType(), "PropertyName");
...
}

ist aber Copy&Paste -fehleranfällig. was aber beim Debugen schnell gefunden werden kann:


   public static void Check (T tValue, Type objType, string propName)
   {
      PropertyInfo pi;

#if DEBUG
      String       strProperty;
      Type         typDeclaring;
      MethodBase   mb;
      //-----------------------------------------------------------------------
      // Ermitteln von welcher Property welcher Klasse wir aufgerufen wurden
      //-----------------------------------------------------------------------
      mb = new StackFrame (1).GetMethod ();

      if (mb.DeclaringType != objType)
         throw new Exception("Der übergebene Typ des Objektes stimmt nicht mit" +
                            " dem Typ des prüfendes Objektes.");

      if (!mb.IsSpecialName) {
         throw new Exception ("PropertyCheckException: "
                            + "Check wurde nicht direkt von einer "
                            + "Property aus aufgerufen");
      }

      strProperty = mb.Name.Substring (4);
      
      if (strProperty != propName)
            throw new Exception("Es wurde falscher PropertyName übergeben");
#endif

      pi = objType.GetProperty (propName,
                                     (mb.IsStatic
                                      ? BindingFlags.Static
                                      : BindingFlags.Instance)
                                   | (mb.IsPublic
                                      ? BindingFlags.Public
                                      : BindingFlags.NonPublic));
.....

1.373 Beiträge seit 2004
vor 17 Jahren

Man könnte natürlich dem setter ein


[MethodImpl(MethodImplOptions.NoInlining)]

verpassen um das inlinen zu verhindern. Aber schöner wird es davon auch nicht 🙂

Grüße,
Andre

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 17 Jahren

Hallo zusammen,

das Feine an so einem Forum ist, dass man nicht alles selber machen muss. Die beiden Vorschläge von Xqgene und VizOne lösen das Problem. Jeder kann jetzt selbst überlegen, was er schöner findet.

Da es mein selbstgesetztes Ziel war, die redundante und "copy&paste-fehleranfällige" Angabe von Property-Namen und -Typen zu vermeiden, würde ich mich persönlich für die Variante von VizOne entscheiden.

Dass man ein zusätzliches Attribut braucht, stört mich nicht. Wenn man das von mir vorgeschlagene Verfahren anwendet, sollte man ohnehin ein Fan von Attributen sein. Dann kommt es aber auch auf ein Attribut mehr oder weniger nicht an.

Und dass die Inlining-Optimierung für den Setter ausgeschaltet ist, macht angesichts des Faktors 4000 den Kohl nun ins keinster Weise fett.

Sollte man das Attribut vergessen, bekommt man im Releasemodus beim Setzen der Property die Exception "Check wurde nicht direkt von einer Property aus aufgerufen". Wenn man das Attribut also nicht gerade bei einer äußerst selten gesetzen Property vergisst, merkt man es sofort beim ersten Echttest.

Ich sehe damit die Herausforderung als bewältigt an. Ich habe im Programm ganz oben das NoInlining-Attribut hinzugefügt. Damit funktioniert das Programm jetzt auch im Releasmodus zuverlässig.

Vielen Dank an VizOne und Xqgene. Bei Xqgene, Talla und svenson möchte ich mich bei dieser Gelegenheit für die Anregungen und Rückmeldungen im Vorfeld bedanken. Und nicht zuletzt geht mein Dank an Kostas, der mich überhaupt erst angestiftet hat, mich mit dem Thema zu beschäftigen.

herbivore

6.862 Beiträge seit 2003
vor 17 Jahren

Die Danksagung liest sich ja wie bei einem Buchtext 🙂 Strebst so eine veröffentlichung mal an?

Baka wa shinanakya naoranai.

Mein XING Profil.

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 17 Jahren

Hallo talla,

Die Danksagung liest sich ja wie bei einem Buchtext

für ein Buch müssten die Danksagungen noch viel länger werden. Mindestens eine halbe Seite. Sonst macht das nichts her. 🙂 Ansonsten Ehre wem Ehre gebührt.

Strebst so eine veröffentlichung mal an?

Mit der Veröffentlichung auf mycsharp bin ich voll und ganz zufrieden. Vielleicht packe ich später mal einige meiner Beiträge von mycsharp überarbeitet und aktuallisiert auf meine (momentan leere) Homepage. Aber das ist Zukunftsmusik.

herbivore

1.985 Beiträge seit 2004
vor 17 Jahren

Hallo zusammen,

Original von herbivore
Mit der Veröffentlichung auf mycsharp bin ich voll und ganz zufrieden.

Ist das Thema nicht schon so "groß" und gut ausgearbeitet, dass man es auf CodeProject veröffentlichen könnte? Nur mal so als Anregung, da das Problem mit den vielen manuell geschriebenen Checks ja eigentlich sehr häufig auftritt.

Gruß,
Fabian

"Eine wirklich gute Idee erkennt man daran, dass ihre Verwirklichung von vornherein ausgeschlossen erscheint." (Albert Einstein)

Gefangen im magischen Viereck zwischen studieren, schreiben, lehren und Ideen umsetzen…

Blog: www.fabiandeitelhoff.de

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 17 Jahren

Hallo Fabian,

kann schon sein, dass der Artikel gut genug für Codeprojekt wäre. Und gerade wenn, finde ich es gut, dass es auf mycsharp (im doppelten Sinne) exklusiven Content gibt.

herbivore

1.985 Beiträge seit 2004
vor 17 Jahren

Hallo herbivore,

Original von herbivore
Und gerade wenn, finde ich es gut, dass es auf mycsharp (im doppelten Sinne) exklusiven Content gibt.

Stimmt, hab' ich gar nicht dran gedacht. Dadurch wird myCSHARP nur noch interessanter.

Gruß,
Fabian

"Eine wirklich gute Idee erkennt man daran, dass ihre Verwirklichung von vornherein ausgeschlossen erscheint." (Albert Einstein)

Gefangen im magischen Viereck zwischen studieren, schreiben, lehren und Ideen umsetzen…

Blog: www.fabiandeitelhoff.de

F
10.010 Beiträge seit 2004
vor 17 Jahren

Ist zwar jetzt schon eine ziemlich Zeit her, aber ich beschäftige mich derzeit
auch mit Reflection und DynamicMethod.

Was in dem Beispiel wirklich die meiste Zeit kostet ist das

 pi = typDeclaring.GetProperty

und das

achkattr = pi.GetCustomAttributes

Wenn Du das Cached geht es deutlich schneller.

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 17 Jahren

Hallo FZelle,

das deckt sich zumindest nicht ganz mit meiner Messung:

Ca. 2/3 der Performance gehen übrigens auf das Konto der Ermittlung von strProperty und typDeclaring und dabei wiederum macht GetStackFrame den Löwenanteil aus.

Die eigentliche Lösung liegt m.E. darin den Aufwand auf die Compilezeit zu verlagern, wie das auch in Event auf Methoden-Aufruf über Attribute (natürlich auch davor und danach) diskuiert wird.

herbivore

F
10.010 Beiträge seit 2004
vor 17 Jahren

Diese Aufrufe finden in der Release Version aber garnicht statt.

Wenn Du also den Release Teil nimmst, ist meine Aussage schon stimmig.

Und natürlich kann man das alles auch zur Compilezeit machen, aber wozu
wenn dieser Ansatz ja eigentlich OK ist.

5.941 Beiträge seit 2005
vor 17 Jahren

Hallo Herbivore

Eine interessante Idee und eine tolle Lösung dazu.
Kiompliment, weiter so 🙂)

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 17 Jahren

Hallo FZelle

Diese Aufrufe finden in der Release Version aber garnicht statt.

mit GetStackFrame ist new StackFrame (1) in Check (T tValue) gemeint und dieser Aufruf findet natürlich auch im Release statt. Die Aufrufe von SayHuhu sind in die Messung natürlich nicht eingeflossen.

Und natürlich kann man das alles auch zur Compilezeit machen, aber wozu wenn dieser Ansatz ja eigentlich OK ist.

Hauptsächlich aus Performance-Gründen, denn ich denke auch durch ein Caching hätte man wegen des new StackFrame immer noch mindestens einen Faktor vor 1000 gegenüber der eincompilierten Version. Außerdem finde ich es eine grundsätzlich eine interessante Idee, den Code zur Compilezeit (oder sagen wir besser zur Buildzeit) instrumentieren zu können; nicht nur für diesen Fall.

Hallo Peter Bucher,

vielem Dank für das Lob!

herbivore

F
10.010 Beiträge seit 2004
vor 17 Jahren

Sorry hatte beim "hochblättern" nur den Vorschlag von Xqgene gesehen,
und da ist das mit "#if debug" ausgeschaltet.

Und ich persönlich halte nichts von Exceptions in Settern, aber das eine andere Sache.
Schon mal IDataErrorInfo angeschaut?

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 17 Jahren

Hallo FZelle,

IDataErrorInfo kannte ich noch nicht. Aber meine Implementierung wäre ja leicht von Exceptions auf IDataErrorInfo anzupassen, wenn das dem eigene Stil entspräche. Mir ging es darum die Prüfung in eine deklarative Form zu bingen, nicht darum, wie die Prüfung das Ergebnis übermittelt.

herbivore

F
10.010 Beiträge seit 2004
vor 17 Jahren

Ja, ist ja richtig, aber die Exceptions bei Settern wiegen Leute immer in trügerische Sicherheit.

Diese Exceptions werden nämlich beim DataBinding meist geschluckt,
weshalb dann ein zustand entsteht, der subobtimal ist.

IDataErrorInfo ist sehr nett, weil ein gebundener Errorprovider dann
zeimlich einfach dem Benutzer seine Fehler mitteilt.

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 16 Jahren

Hallo zusammen,

ich habe mich heute nochmal der Performance angenommen und dazu den Code von ganz oben auf auf die Verwendung von PostSharp (http://www.postsharp.org/) umgestellt. Und ich kann schon verraten: es hat sich gelohnt!

PostSharp, a .NET post-compiler: AOP and more

With PostSharp, you can develop custom attributes that change your code.

Na, das ist doch genau, was wir wollen. 🙂

And you can do more!

PostSharp is a post-compiler: an open platform for analysis and transformations of .NET assemblies after compilation.

Aspect-Oriented Programming (AOP) or Policy Injection are two of the major applications, but only two of them.
PostSharp Laos is a high-level aspect weaver that makes aspect-oriented programming (AOP) extremely easy. But Laos is only an illustration of the complete Platform. PostSharp is used to perform low-level MSIL injection. It serves as a base for persistence layers, optimizers or custom AOP weavers.

Wenn man Postsharp nutzen möchte, muss man es sich natürlich herunterladen und installieren.

Gut, aber zurück zur Umstellung des Codes. Diese war erstaunlich einfach. Die Struktur musste so gut wie gar nicht angepasst werden. Hier was wir uns alles sparen können:*Die Interfaces CheckableAttribute und CheckableAttribute <T> sowie die Klasse AttributeChecker <T>, die ja bisher das Weben implementiert hat, können alle ersatzlos entfallen. *Entsprechend entfällt auch der explizite Aufruf von AttributeChecker <String>.Check (value); das macht Postsharp implizit. Ein sehr angenehmer Nebeneffekt. *Entfallen kann auch das bisherige Attribut [MethodImpl (MethodImplOptions.NoInlining)], das ja nur nötig war, damit die AttributeChecker-Klasse beim Weben im Releasemode nicht durcheinander kam.

Zusätzlich brauchen wir nur das:*Einen neuen Namespace using PostSharp.Laos;

Und ein paar Sachen müssen noch geändert/angepasst werden:*Die Klassen MaxStringLengthAttribute und StringFormatAttribute müssen als [Serializable] gekennzeichnet werden. *Die bisherige Vererbungsliste dieser beiden Klassen (: Attribute, CheckableAttribute <String>) wird ersetzt durch die Postsharp-Oberklasse : OnMethodBoundaryAspect. *Die Methodenköpfe public void Check (PropertyInfo pi, String strValue) werden ersetzt durch die von Postsharp vorgegeben Methodenköpfe public override void OnEntry (MethodExecutionEventArgs e) *Alle weiteren Änderungen ergeben sind draus, dass die für die Überprüfung notwendigen Informationen in den MethodExecutionEventArgs stecken und nicht in den bisherigen Parametern PropertyInfo pi und String strValue. Es sind aber genau die gleichen Informationen (und sogar noch viel mehr) vorhanden, nur anders verpackt.

Das sind zwar eine ganze Reihe von Änderungen, aber im Prinzip eben alles nur Details. Die Struktur bleibt die gleiche, außer dass das explizite Weben durch AttributeChecker-Klasse durch den impliziten Mechanismus von Postsharp ersetzt wird.

So, nun aber zur Performance: Die Version mit Postsharp gegenüber der bisherigen Version um ca. Faktor 40 schneller! Ok, damit sind die Zugriffe zwar immer noch um Faktor 100 langsamer als eine direkte Prüfung, aber wenn man berücksichtigt, dass Postsharp ja für jede Prüfung ein Objekt vom Typ MethodExecutionEventArgs erstellen muss, ist das nicht verwunderlich. Aber nur noch Faktor 100 gegenüber dem bisherigen Faktor 4000 ist ein so großer Fortschritt, dass ich wirklich keine Bedenken hätte, den Mechanismus in realen Programm einzusetzen. Immerhin sind jetzt auf meinem Rechner ca. 800.000 Zugriffe auf Properties pro Sekunde möglich (gegen über den bisherigen 20.000).

Neben der reinen Code-Änderung, muss man Postsharp in den Build-Prozess einbinden, was jedoch leicht von der Hand geht:

Man muss eine Referenz auf PostSharp.Laos.dll und PostSharp.Core.dll zum Projekt hinzufügen und außerdem die erstellte EXE mit Postsharp nachbearbeiten. So wie ich das verstanden habe, geht diese Nachbearbeitung automatisch, wenn man die VS-Integration von Postsharp installiert hat. Ich habe die Nachbearbeitung mit folgender Kommandozeile vorgenommen, die ich der Lesbarkeit wegen auf mehrere Zeilen umgebrochen habe:

PostSharp.exe "C:\Programme\PostSharp 1.0\Default.psproj"
   attr.exe /p:thumbsup:utput=attr_ps.exe
   /p:IntermediateDirectory=c:\windows\temp /p:CleanIntermediate=true
   /p:DependenciesDirectory=.

Mit dieser Zeile wird aus attr.exe die erwünschte endgültige EXE-Datei attr_ps.exe. Man kann zwar auch attr.exe ausführen, aber bei dieser werden die Attribute beim Setzen von Properties nicht berücksichtigt. Das passiert erst in attr_ps.exe.

Hier der vollständige Code der Postsharp-Variante:


using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using PostSharp.Laos; // PS

//*****************************************************************************
// Technische Hilfsklasse
public static class Helper
{
   //==========================================================================
   public static void SayHuhu ()
   {
      // besser Debug.WriteLine
      Console.WriteLine ("==> "
                       + new StackFrame (1).GetMethod ().DeclaringType.Name
                       + "."
                       + new StackFrame (1).GetMethod ().Name);
   }
}

//*****************************************************************************
// Hilfsklasse
[Serializable]
[AttributeUsage (AttributeTargets.Property)]
public class MaxStringLengthAttribute : OnMethodBoundaryAspect
{
   //--------------------------------------------------------------------------
   private int _iMaxStringLength;

   //==========================================================================
   public MaxStringLengthAttribute ()
   {
      Helper.SayHuhu ();
      _iMaxStringLength = -1;
   }

   //==========================================================================
   public MaxStringLengthAttribute (int iMaxStringLength)
   {
      Helper.SayHuhu ();
      _iMaxStringLength = iMaxStringLength;
   }


   //==========================================================================
   public override void OnEntry (MethodExecutionEventArgs e)
   {
      Helper.SayHuhu ();

      //-----------------------------------------------------------------------
      // Argumente holen
      //-----------------------------------------------------------------------
      Object [] aobjArgs = e.GetArguments ();
      if (aobjArgs == null) {
         Console.WriteLine ("getter called");
         return;
      }
      String strValue = (String)aobjArgs [0];

      //-----------------------------------------------------------------------
      // Nötigenfalls Ermitteln der Feldlänge
      //-----------------------------------------------------------------------
      if (_iMaxStringLength < 0) {
         // besser Debug.WriteLine
         Console.WriteLine ("Simulierter Datenbankzugriff, "
                          + "um die Feldlänge zu ermitteln");
         if (e.Method.Name.Substring (4) == "Street") {
            // besser Debug.WriteLine
            Console.WriteLine ("Simulierter Datenbankzugriff erfolgreich");
            _iMaxStringLength = 20;
         }
      }

      //-----------------------------------------------------------------------
      // Eigentliche Prüfung
      //-----------------------------------------------------------------------
      if (strValue.Length > _iMaxStringLength) {
         throw new Exception ("MaxStringLengthException: "
                            + e.Method.DeclaringType.Name
                            + "."
                            + e.Method.Name.Substring (4)
                            + @": """
                            + strValue
                            + @""" ist "
                            + strValue.Length
                            + " Zeichen lang. Erlaubt sind aber nur "
                            + _iMaxStringLength
                            + " Zeichen.");
      }
   }
}

//*****************************************************************************
// Hilfsklasse
[Serializable]
[AttributeUsage (AttributeTargets.Property)]
public class StringFormatAttribute : OnMethodBoundaryAspect
{
   //--------------------------------------------------------------------------
   private String _strPattern;

   //==========================================================================
   public StringFormatAttribute (String strPattern)
   {
      Helper.SayHuhu ();
      _strPattern = strPattern;
   }

   //==========================================================================
   public override void OnEntry (MethodExecutionEventArgs e)
   {
      Helper.SayHuhu ();

      //-----------------------------------------------------------------------
      // Argumente holen
      //-----------------------------------------------------------------------
      Object [] aobjArgs = e.GetArguments ();
      if (aobjArgs == null) {
         Console.WriteLine ("getter called");
         return;
      }
      String strValue = (String)aobjArgs [0];

      //-----------------------------------------------------------------------
      // Eigentliche Prüfung
      //-----------------------------------------------------------------------
      if (!Regex.IsMatch (strValue, "^" + _strPattern + "$")) {
         throw new Exception ("StringFormatException: "
                            + e.Method.DeclaringType.Name
                            + "."
                            + e.Method.Name.Substring (4)
                            + @": """
                            + strValue
                            + @""" entsprich nicht dem Muster ""^"
                            + _strPattern
                            + @"$"".");
      }
   }
}

//*****************************************************************************
// Modellklasse
public class Address
{
   //--------------------------------------------------------------------------
   private String _strStreet;

   //==========================================================================
   public Address ()
   {
      Helper.SayHuhu ();
   }

   //==========================================================================
   [MaxStringLength ()]
   [StringFormat ("[a-zA-ZäöüÄÖÜß ]*")]
   public String Street
   {
      get {
         Helper.SayHuhu ();
         return _strStreet;
      }

      set {
         Helper.SayHuhu ();
         _strStreet = value;
       }
   }
}

//*****************************************************************************
// Hauptklasse
static class App
{
   //==========================================================================
   public static void Main (string [] astrArg)
   {
      Helper.SayHuhu ();

      //-----------------------------------------------------------------------
      Address addr = new Address ();

      //-----------------------------------------------------------------------
      Console.WriteLine ("");
      Console.WriteLine ("Setze korrekten Straßennamen");
      try {
         addr.Street = "Kurz genug";
      }
      catch (Exception exc) {
         Console.WriteLine (exc.Message);
      }

      //-----------------------------------------------------------------------
      Console.WriteLine ("");
      Console.WriteLine ("Setze zu langen Straßennamen");
      try {
         addr.Street = "Dieser Straßenname ist zu lang";

      }
      catch (Exception exc) {
         Console.WriteLine (exc.Message);
      }

      //-----------------------------------------------------------------------
      Console.WriteLine ("");
      Console.WriteLine ("Setze Straßennamen mit ungültigen Zeichen");
      try {
         addr.Street = "!õ$%&/(";
      }
      catch (Exception exc) {
         Console.WriteLine (exc.Message);
      }
   }
}

herbivore

369 Beiträge seit 2006
vor 16 Jahren

Ist bekannt, ob Microsoft vorhat deklarative Parameterüberprüfung mit Visual Studio "Orcas" / .Net 3.5 einzuführen? Zeigt Microsoft Ambitionen sich dem Thema AOP (Aspect Oriented Programming) anzunehmen?

Hat sich von euch schonmal jemand mit Phx.Morph auseinandergesetzt (http://www.columbia.edu/~me133/ , http://www.codeplex.com/phoenixs1)?

2.921 Beiträge seit 2005
vor 16 Jahren

@Kabelsalat: Nein, die zeigen da keine Ambitionen bisher, da so etwas ähnliches in der EnterpriseLib schon existiert (Policy-Injection und ähnliches)

Auch der Versuch Enhancer Technologien nutzbar zu machen im 3.x .NET Framework bleibt bisher aus. Ebenso unterstützt dies auch die Sprach-Spezifikation C# 3.0 nicht.

Bleib bisher nur auf PostSharp und ähnliche oder selbst implementierte Compile-Techniken auszuweichen.

Das Problem ist ja nur, was Herbivore auch beschreibt, dass nur über Stackframe und den Aufruf der Helperklasse möglich ist, die Attribute auch aufzurufen, während man sich z.B. in den Properties befindet. PostSharp injiziert diesen Code, um die Attribute gleich, wenn sie benötigt werden, aufzurufen.

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

I
1.739 Beiträge seit 2005
vor 16 Jahren

kann schon sein, dass der Artikel gut genug für Codeprojekt wäre. Und gerade wenn, finde ich es gut, dass es auf mycsharp (im doppelten Sinne) exklusiven Content gibt.

Dem schliesse ich mich gerne an. Nur den doppelten Sinn versteh ich gerade nicht(mir genügt ein einfacher).

Das Performanceproblem(Faktor 4000 halte ich für übertrieben) liegt m.E. am Test-Modell. Attribute sind eine schöne Sache, nur muss nicht jedesmal eine neue Instanz nachgeladen werden um diese auch noch zu reflektieren.

  
public String Street  
{  
   ...  
   set {  
      If (value.Length > 20) { // <= Implementierung einer Prüfung  
         throw new Exception ("Zu lang");  
      }  
      _strStreet = value;  
    }  
}  
  

Dies hat mehrere Nachteile. Aus Sicht des Implementierer der Property wird der Code - insbesondere wenn mehrere Prüfungen erforderlich sind - unnötig aufgebläht und gleichzeitig redundant. Aus Sicht des Benutzers der Property verschwinden die Prüfungen in der Implementierung und sind nach außen nicht bekannt.

Mal abgesehen vom DescriptionAttribut, was hindert jemanden beim Setter auf eine andere Klasse zuzugreifen?

Ich halte dein Beispiel für sehr gut, in Sachen dass Attribute näher gebracht werden. Ob Regeln als Aspekt gehandhabt werden sollten, ist eher philosophisch. Im praktischen Sinn, ist es per Attribut oft zu langsam. (Faktor 4000 ist aber understatement(nicht nachvollziehbar)).
Halte ich es ganz einfach: verschliesst sich der Sinn der Trennung im Beispiel. Es existiert eine Property, dafür muss aber man eine eigene Klasse schreiben...
Existierte sowas wie Dynamik(oder würde erklärt) zur Laufzeit o.ä. würden mehr den Nutzen erkennen.

Fazit: ich find's gut weil der Nutzen von Attributen gut erklärt wird. Andere die Lösung selbst. Aber ich fürchte die meisten haben es nicht verstanden.
Technich ok, aber etwas unverständlich für Anfänger bzw. Fortgeschrittene Programmierer denen Design einfach abgeht.

2.921 Beiträge seit 2005
vor 16 Jahren

@ikaros: Ich finde es nicht unverständlich.
Ausserdem sind Attribute meiner Meinung nach eine gute Möglichkeit deklarative Programmierung zu benutzen (also auch im Sinne von AOP aber nicht nur), wenn es Richtung Enhancer geht, unterstützt Microsoft dies immer noch nicht. Auch im neuesten Framework bzw. Compiler ist nichts dazu vorhanden, wenn man mal die Enterprise-Library ausser acht läßt, die ja auch nur quasi Runtime-Proxies bietet.

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

I
1.739 Beiträge seit 2005
vor 16 Jahren

@dr4g0n76:
Deiner Meinung zu Attributen schliesse ich mich gern an. Wie schon erwähnt finde ich die angewandte Technik auch nicht schlecht. Im Gegenteil. Ich hoffte mich da verständlich ausgedrückt zu haben.
Herbivore hat gute Arbeit geliefert. Nur finde ich das das Beispiel nicht ganz passt. Für die geschilderte Problemstellung finde ich die Lösung(trotz der Klasse) als 2.e Wahl. Grund ist: das Problem selbst lässt sich trivialer lösen(einmal unschön), besser: anders(fast attributslos). Bezogen auf das Beispiel lassen sich halt zuviele Alternativen finden, die sich auch in kürzerer Codeform wiederspiegeln lassen.
Das ist der Punkt den ich etwas kritisiere. Die angewandte Technik erschliesst sich daurch nicht unbedingt dem Neuling(dieser propagierten Technik). Das liegt aussschliesslich am Beispiel.

Übrigens, was mich ein wenig nervt: Ist die quasi nicht vorhandene Trennung zwischen Artikel und Diskussion.
Ich hab das Gefühl das zumindest in diesen Artikelthread der Artikel unter der Diskussion leidet.

2.921 Beiträge seit 2005
vor 16 Jahren

@Herbivore:

Was mir nicht ganz klar ist, warum Du die Helperklasse noch benutzt, reicht nicht auch folgendes aus:


using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using PostSharp.Laos; // PS

//*****************************************************************************
// Hilfsklasse
[Serializable]
[AttributeUsage(AttributeTargets.Property)]
public class MaxStringLengthAttribute : OnMethodBoundaryAspect
{
    //--------------------------------------------------------------------------
    private int _iMaxStringLength;
    //==========================================================================
    public MaxStringLengthAttribute()
    {
        //Helper.SayHuhu();
        _iMaxStringLength = -1;
    }

    //==========================================================================
    public MaxStringLengthAttribute(int iMaxStringLength)
    {
        //Helper.SayHuhu(e);
        _iMaxStringLength = iMaxStringLength;
    }


    //==========================================================================
    public override void OnEntry(MethodExecutionEventArgs e)
    {
        // besser Debug.WriteLine
        Console.WriteLine ("==> "
                       + e.Method.DeclaringType.Name
                       + "."
                       + e.Method.Name);

         //-----------------------------------------------------------------------
        // Argumente holen
        //-----------------------------------------------------------------------
        Object[] aobjArgs = e.GetArguments();
        if (aobjArgs == null)
        {
            Console.WriteLine("getter called");
            return;
        }
        String strValue = (String)aobjArgs[0];

        //-----------------------------------------------------------------------
        // Nötigenfalls Ermitteln der Feldlänge
        //-----------------------------------------------------------------------
        if (_iMaxStringLength < 0)
        {
            // besser Debug.WriteLine
            Console.WriteLine("Simulierter Datenbankzugriff, "
                             + "um die Feldlänge zu ermitteln");
            if (e.Method.Name.Substring(4) == "Street")
            {
                // besser Debug.WriteLine
                Console.WriteLine("Simulierter Datenbankzugriff erfolgreich");
                _iMaxStringLength = 20;
            }
        }

        //-----------------------------------------------------------------------
        // Eigentliche Prüfung
        //-----------------------------------------------------------------------
        if (strValue.Length > _iMaxStringLength)
        {
            throw new Exception("MaxStringLengthException: "
                               + e.Method.DeclaringType.Name
                               + "."
                               + e.Method.Name.Substring(4)
                               + @": """
                               + strValue
                               + @""" ist "
                               + strValue.Length
                               + " Zeichen lang. Erlaubt sind aber nur "
                               + _iMaxStringLength
                               + " Zeichen.");
        }
    }
}

//*****************************************************************************
// Hilfsklasse
[Serializable]
[AttributeUsage(AttributeTargets.Property)]
public class StringFormatAttribute : OnMethodBoundaryAspect
{
    //--------------------------------------------------------------------------
    private String _strPattern;
    //==========================================================================
    public StringFormatAttribute(String strPattern)
    {
        //Helper.SayHuhu();
        _strPattern = strPattern;
    }

    //==========================================================================
    public override void OnEntry(MethodExecutionEventArgs e)
    {
        Console.WriteLine("==> "
                       + e.Method.DeclaringType.Name
                       + "."
                       + e.Method.Name);

        //-----------------------------------------------------------------------
        // Argumente holen
        //-----------------------------------------------------------------------
        Object[] aobjArgs = e.GetArguments();
        if (aobjArgs == null)
        {
            Console.WriteLine("getter called");
            return;
        }
        String strValue = (String)aobjArgs[0];

        //-----------------------------------------------------------------------
        // Eigentliche Prüfung
        //-----------------------------------------------------------------------
        if (!Regex.IsMatch(strValue, "^" + _strPattern + "$"))
        {
            throw new Exception("StringFormatException: "
                               + e.Method.DeclaringType.Name
                               + "."
                               + e.Method.Name.Substring(4)
                               + @": """
                               + strValue
                               + @""" entsprich nicht dem Muster ""^"
                               + _strPattern
                               + @"$"".");
        }
    }
}

//*****************************************************************************
// Modellklasse
public class Address
{
    //--------------------------------------------------------------------------
    private String _strStreet;

    //==========================================================================
    public Address()
    {
        //Helper.SayHuhu();
    }

    //==========================================================================
    [MaxStringLength()]
    [StringFormat("[a-zA-ZäöüÄÖÜß ]*")]
    public String Street
    {
        get
        {
            //Helper.SayHuhu();
            return _strStreet;
        }

        set
        {
            //Helper.SayHuhu();
            _strStreet = value;
        }
    }
}

//*****************************************************************************
// Hauptklasse
static class App
{
    //==========================================================================
    public static void Main(string[] astrArg)
    {
        //Helper.SayHuhu();

        //-----------------------------------------------------------------------
        Address addr = new Address();

        //-----------------------------------------------------------------------
        Console.WriteLine("");
        Console.WriteLine("Setze korrekten Straßennamen");
        try
        {
            addr.Street = "Kurz genug";
        }
        catch (Exception exc)
        {
            Console.WriteLine(exc.Message);
        }

        //-----------------------------------------------------------------------
        Console.WriteLine("");
        Console.WriteLine("Setze zu langen Straßennamen");
        try
        {
            addr.Street = "Dieser Straßenname ist zu lang";
        }
        catch (Exception exc)
        {
            Console.WriteLine(exc.Message);
        }

        //-----------------------------------------------------------------------
        Console.WriteLine("");
        Console.WriteLine("Setze Straßennamen mit ungültigen Zeichen");
        try
        {
            addr.Street = "!õ$%&/(";
        }
        catch (Exception exc)
        {
            Console.WriteLine(exc.Message);
        }
        Console.ReadKey();
    }
}

Wie man hier sieht ist die Helperklasse verschwunden, die beiden identischen Aufrufe von


        Console.WriteLine("==> "
                       + e.Method.DeclaringType.Name
                       + "."
                       + e.Method.Name);

könnte man natürlich auch wieder in einer Funktion zusammenfassen,

Wenn man natürlich das Attribut an sich betrachtet:

z.B. den Konstruktor von


    public StringFormatAttribute(String strPattern)
    {
        Helper.SayHuhu();
        _strPattern = strPattern;
    }

ist das sicherlich richtig, denn im Attribut geht das zumindest so weit ich weiss nicht, und genau so wenn Helper.SayHuhu() nach der ersten Codezeile in einer Funktion ausserhalb eines Attributes stehen sollte, oder vor der letzten Codezeile ausserhalb eines Attributes...

aber ansonsten denke ich es wird nicht mehr benötigt, oder?

Außerdem kann man ja, wie man sieht über MethodExecutionEventArgs die entsprechenden Parameter im Attribut auslesen und ausgeben.

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 16 Jahren

Hallo dr4g0n76,

Helper ist wie der Name sagt eine Hilfsklasse. Der Focus meiner Lösung liegt nicht auf der Helper-Klasse. Sie wird nur benötigt, um den Ablauf zu demonstrieren. Im produktiven Code wäre die Helper-Klasse nicht enthalten.

In meinem Postscharp-Beitrag wollte ich zeigen, was man gegenüber der ursprünglichen Version minimal ändern muss, um auf Postsharp umzustellen. Entsprechend habe ich die Helper-Klasse unverändert gelassen.

Die Helper-Klasse nur eine einfache Unterstützung für's Tracing. Dass man Tracing auch und sogar besser über Postsharp realisieren kann, ist nicht Gegenstand des Artikels.

herbivore

2.921 Beiträge seit 2005
vor 16 Jahren

gut. hab ich verstanden.

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

.
332 Beiträge seit 2006
vor 16 Jahren

Original von herbivore

Leider bin ich bei der Performance-Analyse auf eine weitere Herausforderung gestoßen. Im Moment funktioniert mein Code nur im Debug-Modus zuverlässig, da im Release-Modus scheinbar die Aufrufe des Setters wegoptimiert werden (inline). Es gibt dann also keinen Stackframe für den Aufruf des Setters und entsprechend liefert GetStackframe (1) den Stackframe der Methode, die den Setter aufruft. Damit schlägt aber die Ermittlung von strProperty und typDeclaring fehl. Die Prüfung kann nicht durchgeführt werden. Leider werde ich mich vermutlich erst in zwei Wochen diesem Thema wieder intensiv widmen können.

herbivore

Hallo, ich habe leider noch nicht wirklich viel mit Attributen gemacht. Der Artikel hat mir aber sehr geholfen dem Thema Attribute und AOP etwas näher zu kommen.

Mir ist leider nicht wirklich klar wieso der Compiler etwas wegoptimiert und wieso es durch "NoInlining" behoben wird.

Vielen Dank
.tim

F
10.010 Beiträge seit 2004
vor 16 Jahren

Der o.a. Code benötigt bei Fehlern den Stackframe des Property Setters/Getters.

Der ILCompiler vom FW 2.0 kann aber Property Setter/Getter inline Expandiieren,
dann fehlt der Stackframe, also macht es Probleme.

Ergo, Inlining aus == dieses Problem behoben.

Auf das andere ( Binding schluck meist Exceptions) hatte ich witer oben schon hingewiesen.

.
332 Beiträge seit 2006
vor 16 Jahren

Original von FZelle
Der ILCompiler vom FW 2.0 kann aber Property Setter/Getter inline Expandiieren,
dann fehlt der Stackframe, also macht es Probleme.

Was genau kann ich mir unter "inline Expandiieren" vorstellen?

Das mit dem Binding ist mir klar, danke.

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 16 Jahren

Hallo .tim,

die Diskussion um Inlining hat ja nur sehr am Rande mit den Artikel zu tun. Deshalb habe hoffe ich, dass wir sie mit diesem Post beenden können.

Inlining bedeutet, dass der Compiler an der Stelle, wo eine Methode aufgerufen werden soll, stattdessen direkt den Code der Methode einsetzt. Dadurch wird die Laufzeit für den Aufruf der Methode gespart. Das ist vor allem bei sehr kurzen Methoden sinnvoll.

herbivore

.
332 Beiträge seit 2006
vor 16 Jahren

Vielen Dank jetzt verstehe ich was die Problematik ist. 🙂

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 14 Jahren

Hallo zusammen,

es gibt eine einfache Lösung, den Namen der Property (bzw. deren Setters) zu ermitteln, die resistent gegen Inlining ist: MethodBase.GetCurrentMethod ().Name. Der Preis dafür ist, dass diese Ermittlung nicht in der Check-Methode erfolgen kann, sondern innerhalb der Property selbst erfolgen muss und das Ergebnis dann per Parameter an die Check-Methode überheben werden muss.

Um die Unterschiede bezüglich Inlining zwischen der bisher verwendete Lösung StackFrame (n).GetMethod ().Name der Lösung mit MethodBase.GetCurrentMethod ().Name zu testen, habe ich folgendes Programm geschrieben:

using System;
using System.Reflection;
using System.Diagnostics;

public class A
{
   public A ()
   {
   }

   public String Value1
   {
      get { return MethodBase.GetCurrentMethod().Name; }
   }

   public String Value2
   {
      get { return new StackFrame (0).GetMethod ().Name; }
   }

   public void M ()
   {
      String str;

      str = Value1;
      Console.WriteLine (str);
      str = Value2;
      Console.WriteLine (str);
   }
}

static class App
{
   public static void Main (string [] astrArg)
   {
      new A ().M ();
   }
}

Das Programm gibt im Debug-Modus - also ohne Inlining - folgendes aus:

get_Value1
get_Value2

im Releasemodus dagegen

get_Value1
Main

Das heißt für mich, dass entweder die Verwendung von MethodBase.GetCurrentMethod das Inlining verhindert oder - was ich für wahrscheinlicher halte - dass MethodBase.GetCurrentMethod trotz Inlining so schlau ist, den korrekten Namen zu ermitteln. Wie dem auch sei: In beiden Fällen ist MethodBase.GetCurrentMethod durch Fehler wegen Inlining gefeiht.

Mit einer kleinen Änderung der Check-Methode und um den Preis, jedem Aufruf der Check-Methode MethodBase.GetCurrentMethod ().Name per Parameter zu übergeben, kann man sich also das Ausschalten des Inlining (mittels [MethodImpl (MethodImplOptions.NoInlining)] vor dem Setter) sparen und verbessert zugleich die Performace deutlich. Nicht nur wegen des Inlinings, sondern auch weil MethodBase.GetCurrentMethod ().Name deutlich schneller berechnet wird als StackFrame (n).GetMethod ().Name.

Auf die Lösung mit MethodBase.GetCurrentMethod ().Name bin ich durch [Artikel] INotifyPropertyChanged implementieren gestoßen.

herbivore

2.921 Beiträge seit 2005
vor 11 Jahren

@Herbivore:

angenommen es geht um gleich geartete Properties: Benutzt Du denn diese Art von Vorgehensweise immer in Deinem Code?

Wenn ja, ist klar (s. z.B. aufgelistete Vorteile),
wenn nein, würde mich interessieren warum nicht.

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 11 Jahren

Hallo dr4g0n76,

momentan benutze ich .NET nur privat für kleine Progrämmchen oder Tools, keine Business-Anwendungen. Deshalb fehlt es an den Voraussetzungen für deine Frage. Unabhängig davon, halte ich den beschriebenen Ansatz für sinnvoll und sowohl praxisrelevant als auch praxistauglich. Trotzdem kann es - und das scheinst du ja wissen zu wollen - Kontraindikationen geben. Hier wären insbesondere von manchen Entwicklern präferierten weichen Datenobjekte bzw. ActiveRecords zu nennen, siehe 3-Schichten-Design?? [harte vs. weiche (Daten-)Objekte / OOP vs. ActiveRecods] ff. Natürlich bin ich mir der Probleme bewusst, die es bereiten kann, wenn man eine Klasse so programmiert, dass deren Objekte zu jedem Zeitpunkt vollständig konsistent sein soll, aber dennoch präferiere diese Vorgehensweise, insbesondere weil ich in dieser Kapselung ein Kernprinzip der Objektorientierung sehe. Mein Vorschlag in diesem Artikel ist eine gute Möglichkeit, das sicherzustellen.

herbivore

2.921 Beiträge seit 2005
vor 11 Jahren

Genau das wollte ich wissen, perfekt beantwortet.

Danke.

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 10 Jahren

Hallo zusammen,

bevor ich es vergesse: Ab .NET 4.5 gibt es noch eine schnellere und zuverlässigere Möglichkeit, zu ermitteln, aus welchem Attribut die Prüfung aufgerufen wurde, die außerdem direkt den Property-Namen liefert und nicht wie die anderen bisher beschriebenen Möglichkeiten nur den Namen des Setters, von dem man noch das "set_" abschneiden muss.

Die Syntax ist zwar etwas komisch, aber die Vorteile unbestritten.

Genaueres dazu gibt es in diesem Thread: Suche Variable für Filename oder Methode für optionale Argumente (C# 5.0?).

herbivore