myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
   » Plugin für Firefox
   » Plugin für IE
   » Gadget für Windows
» Regeln
» Wie poste ich richtig?
» Datenschutzerklärung
» wbb-FAQ

Mitglieder
» Liste / Suche
» Stadt / Anleitung dazu
» Wer ist wo online?

Angebote
» ASP.NET Webspace
» Bücher
» Zeitschriften
   » dot.net magazin

Ressourcen
» guide to C#
» openbook: Visual C#
» openbook: OO
» MSDN Webcasts
» Search.Net

Team
» Kontakt
» Übersicht
» Wir über uns
» Impressum

» Unsere MiniCity
MiniCity
» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Knowledge Base » Artikel » [Artikel] Attribute zur Prüfung von Properties verwenden
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Seiten (2): [1] 2 nächste » Antwort erstellen
Zum Ende der Seite springen  

[Artikel] Attribute zur Prüfung von Properties verwenden

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

[Artikel] Attribute zur Prüfung von Properties verwenden

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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.

C#-Code:
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.

C#-Code:
[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.

C#-Code:
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
Neuer Beitrag 06.04.2006 10:23 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
svenson svenson ist männlich
myCSharp.de-Poweruser/ Experte

Dabei seit: 15.04.2005
Beiträge: 8.746
Entwicklungsumgebung: Visual Studio .NET 2003
Herkunft: Berlin


svenson ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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

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

Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von svenson am 06.04.2006 17:16.

Neuer Beitrag 06.04.2006 16:24 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
frisch frisch ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-1724.gif


Dabei seit: 18.08.2005
Beiträge: 2.082
Entwicklungsumgebung: VS C# 2005 Express
Herkunft: Coburg / Oberfranken


frisch ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo herbivore,

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


Großes Lob! Daumen hoch
Neuer Beitrag 06.04.2006 16:49 Beiträge des Benutzers | zu Buddylist hinzufügen
Xqgene
myCSharp.de-Poweruser/ Experte

Dabei seit: 29.04.2004
Beiträge: 2.051


Xqgene ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

dem kann ich auch nix mehr hinzufügen. Augenzwinkern
Neuer Beitrag 06.04.2006 17:01 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Fabian Fabian ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-1590.jpg


Dabei seit: 09.12.2004
Beiträge: 1.985
Entwicklungsumgebung: Visual Studio 2010
Herkunft: Dortmund


Fabian ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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
Neuer Beitrag 06.04.2006 17:22 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo zusammen,

erstmal vielen Dank für die vielen freundlichen Antworten.

Zitat:
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
Neuer Beitrag 06.04.2006 20:08 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
der Marcel der Marcel ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-1860.gif


Dabei seit: 11.02.2006
Beiträge: 564
Entwicklungsumgebung: Visual Studio 2005
Herkunft: Dresden


der Marcel ist offline Füge der Marcel Deiner Kontaktliste hinzu

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo herbivore!

Daumen hoch

der Marcel
Neuer Beitrag 06.04.2006 21:16 Beiträge des Benutzers | zu Buddylist hinzufügen
Fabian Fabian ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-1590.jpg


Dabei seit: 09.12.2004
Beiträge: 1.985
Entwicklungsumgebung: Visual Studio 2010
Herkunft: Dortmund


Fabian ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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


Gruß,
Fabian
Neuer Beitrag 07.04.2006 09:45 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Xqgene
myCSharp.de-Poweruser/ Experte

Dabei seit: 29.04.2004
Beiträge: 2.051


Xqgene ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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

*g* mich auch.

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

C#-Code:
set
{
...
         AttributeChecker <String>.Check (value, this.GetType(), "PropertyName");
...
}

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

C#-Code:
   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));
.....
Neuer Beitrag 07.04.2006 10:21 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
VizOne VizOne ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-1563.gif


Dabei seit: 26.05.2004
Beiträge: 1.373
Entwicklungsumgebung: VS 2010


VizOne ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Man könnte natürlich dem setter ein

C#-Code:
[MethodImpl(MethodImplOptions.NoInlining)]

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

Grüße,
Andre
Neuer Beitrag 07.04.2006 10:36 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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
Neuer Beitrag 07.04.2006 14:46 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
talla talla ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-3214.jpg


Dabei seit: 20.07.2003
Beiträge: 6.862
Entwicklungsumgebung: VS 2010
Herkunft: Esslingen


talla ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Die Danksagung liest sich ja wie bei einem Buchtext smile Strebst so eine veröffentlichung mal an?
Neuer Beitrag 07.04.2006 14:53 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo talla,

Zitat:
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.

Zitat:
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
Neuer Beitrag 07.04.2006 15:02 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Fabian Fabian ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-1590.jpg


Dabei seit: 09.12.2004
Beiträge: 1.985
Entwicklungsumgebung: Visual Studio 2010
Herkunft: Dortmund


Fabian ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo zusammen,

Zitat:
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
Neuer Beitrag 07.04.2006 16:42 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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
Neuer Beitrag 07.04.2006 16:52 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Fabian Fabian ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-1590.jpg


Dabei seit: 09.12.2004
Beiträge: 1.985
Entwicklungsumgebung: Visual Studio 2010
Herkunft: Dortmund


Fabian ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo herbivore,

Zitat:
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
Neuer Beitrag 07.04.2006 20:00 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 9 Monate.
FZelle
myCSharp.de-Poweruser/ Experte

Dabei seit: 23.04.2004
Beiträge: 9.693


FZelle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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

C#-Code:
pi = typDeclaring.GetProperty

und das

C#-Code:
achkattr = pi.GetCustomAttributes

Wenn Du das Cached geht es deutlich schneller.
Neuer Beitrag 26.01.2007 13:00 Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo FZelle,

das deckt sich zumindest nicht ganz mit meiner Messung:

Zitat:
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
Neuer Beitrag 26.01.2007 13:05 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
FZelle
myCSharp.de-Poweruser/ Experte

Dabei seit: 23.04.2004
Beiträge: 9.693


FZelle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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.
Neuer Beitrag 26.01.2007 14:58 Beiträge des Benutzers | zu Buddylist hinzufügen
Peter Bucher Peter Bucher ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2785.jpg


Dabei seit: 17.03.2005
Beiträge: 5.872
Entwicklungsumgebung: VS08
Herkunft: Zentralschweiz


Peter Bucher ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Herbivore

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


Gruss Peter
Neuer Beitrag 26.01.2007 20:03 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo FZelle

Zitat:
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.

Zitat:
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
Neuer Beitrag 27.01.2007 10:55 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
FZelle
myCSharp.de-Poweruser/ Experte

Dabei seit: 23.04.2004
Beiträge: 9.693


FZelle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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?
Neuer Beitrag 27.01.2007 12:24 Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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
Neuer Beitrag 27.01.2007 12:50 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
FZelle
myCSharp.de-Poweruser/ Experte

Dabei seit: 23.04.2004
Beiträge: 9.693


FZelle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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.
Neuer Beitrag 27.01.2007 14:06 Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 2 Monate.
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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!

Zitat:
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. :-)

Zitat:
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:

Code:
1:
2:
3:
4:
PostSharp.exe "C:\Programme\PostSharp 1.0\Default.psproj"
   attr.exe /p:Output=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:

C#-Code:
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
Neuer Beitrag 24.04.2007 22:33 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Kabelsalat
myCSharp.de-Mitglied

images/avatars/avatar-1937.jpg


Dabei seit: 08.06.2006
Beiträge: 369
Herkunft: Bodensee


Kabelsalat ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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)?

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Kabelsalat am 28.04.2007 21:29.

Neuer Beitrag 28.04.2007 21:26 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.849
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland


dr4g0n76 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von dr4g0n76 am 29.04.2007 14:27.

Neuer Beitrag 29.04.2007 14:25 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
ikaros
myCSharp.de-Mitglied

Dabei seit: 27.05.2005
Beiträge: 1.738


ikaros ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat:
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.

Zitat:
C#-Code:
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.
Neuer Beitrag 30.04.2007 23:27 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.849
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland


dr4g0n76 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

@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.
Neuer Beitrag 01.05.2007 02:37 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
ikaros
myCSharp.de-Mitglied

Dabei seit: 27.05.2005
Beiträge: 1.738


ikaros ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

@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.
Neuer Beitrag 10.05.2007 22:09 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.849
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland


dr4g0n76 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

@Herbivore:

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

C#-Code:
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

C#-Code:
        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

C#-Code:
    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.
Neuer Beitrag 15.05.2007 09:59 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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
Neuer Beitrag 15.05.2007 14:24 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.849
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland


dr4g0n76 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

gut. hab ich verstanden.
Neuer Beitrag 15.05.2007 15:55 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
.tim .tim ist männlich
myCSharp.de-Mitglied

Dabei seit: 21.12.2006
Beiträge: 332
Entwicklungsumgebung: VS2005
Herkunft: Mainz


.tim ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat:
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
Neuer Beitrag 23.05.2007 11:27 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
FZelle
myCSharp.de-Poweruser/ Experte

Dabei seit: 23.04.2004
Beiträge: 9.693


FZelle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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.
Neuer Beitrag 23.05.2007 13:27 Beiträge des Benutzers | zu Buddylist hinzufügen
.tim .tim ist männlich
myCSharp.de-Mitglied

Dabei seit: 21.12.2006
Beiträge: 332
Entwicklungsumgebung: VS2005
Herkunft: Mainz


.tim ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat:
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.
Neuer Beitrag 23.05.2007 14:09 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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
Neuer Beitrag 23.05.2007 14:55 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
.tim .tim ist männlich
myCSharp.de-Mitglied

Dabei seit: 21.12.2006
Beiträge: 332
Entwicklungsumgebung: VS2005
Herkunft: Mainz


.tim ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Vielen Dank jetzt verstehe ich was die Problematik ist. smile
Neuer Beitrag 23.05.2007 15:11 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 2 Jahre.
herbivore
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.341
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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:

C#-Code:
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
Neuer Beitrag 19.03.2010 08:59 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 2 Jahre.
dr4g0n76
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.849
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland


dr4g0n76 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

@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.
Neuer Beitrag 19.07.2012 12:00 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Seiten (2): [1] 2 nächste » Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 11 Jahre.
Der letzte Beitrag ist älter als 4 Jahre.
Antwort erstellen


© Copyright 2003-2017 myCSharp.de-Team. Alle Rechte vorbehalten. 28.06.2017 16:09