Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
[Artikel] Attribute zur Prüfung von Properties verwenden
dr4g0n76
myCSharp.de - Experte

Avatar #avatar-1768.jpg


Dabei seit:
Beiträge: 3047
Herkunft: Deutschland

beantworten | zitieren | melden

@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.
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

Themenstarter:

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
dr4g0n76
myCSharp.de - Experte

Avatar #avatar-1768.jpg


Dabei seit:
Beiträge: 3047
Herkunft: Deutschland

beantworten | zitieren | melden

gut. hab ich verstanden.
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.
private Nachricht | Beiträge des Benutzers
.tim
myCSharp.de - Member



Dabei seit:
Beiträge: 332
Herkunft: Mainz

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
FZelle
myCSharp.de - Experte



Dabei seit:
Beiträge: 10074

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
.tim
myCSharp.de - Member



Dabei seit:
Beiträge: 332
Herkunft: Mainz

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

Themenstarter:

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
.tim
myCSharp.de - Member



Dabei seit:
Beiträge: 332
Herkunft: Mainz

beantworten | zitieren | melden

Vielen Dank jetzt verstehe ich was die Problematik ist.
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

Themenstarter:

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
dr4g0n76
myCSharp.de - Experte

Avatar #avatar-1768.jpg


Dabei seit:
Beiträge: 3047
Herkunft: Deutschland

beantworten | zitieren | melden

@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.
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

Themenstarter:

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
dr4g0n76
myCSharp.de - Experte

Avatar #avatar-1768.jpg


Dabei seit:
Beiträge: 3047
Herkunft: Deutschland

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

Themenstarter:

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers