Rainer's profileCyrons BlogPhotosBlogListsMore Tools Help

Blog


    May 27

    Skydrive endlich auch in good ol' germany!

    Na endlich kann ich auch die eine oder andere Demo-Applikation zum Download stellen!

    May 25

    Dazu benutzt man Interfaces

    Als Anfänger hatte ich mir lange die Frage gestellt, wozu Interfaces überhaupt dienen. Irgendwie fehlte mir immer das Verständnis dafür. Nachdem ich mich nun einige Zeit mit Design Patterns beschäftigt habe, fiel irgendwann der Groschen. Ich denke, anderen Anfängern geht es auch nicht besser. Vielleicht kann dieser Artikel etwas Licht in die Dunkelheit bringen. Der folgende Artikel setzt ein paar generelle Grundkenntnisse voraus (Instantiierung eines Interfaces, Kenntnis der List<T>-Klasse). Am Ende des Beispiels schreibe ich noch ein paar Worte dazu.

    using System;
    using System.Collections.Generic;
    
    namespace InterfacesDazu
    {
       //======================================================================
    
       class Program
       {
          static void Main()
          {
             IMyInterface interfaceObject = new MyClass();
             Console.WriteLine(interfaceObject.GiveSomethingBack());
             interfaceObject = new MyOtherClass();
             Console.WriteLine(interfaceObject.GiveSomethingBack());
    
             // Etwas komplexer, dafür eventuell einleuchtender ->
             List<IMyInterface> myList = new List<IMyInterface>();
             myList.Add(new MyClass());
             myList.Add(new MyOtherClass());
             foreach(IMyInterface item in myList)
             {
                Console.WriteLine(item.GiveSomethingBack());
             }
             // <- Beides erzeugt die gleiche Ausgabe.
    
             // Verhindert das selbsttätige Schließen des Konsolenfensters.
             Console.WriteLine("\nPress any key to terminate the program.");
             Console.ReadKey();
          }
       }
    
       //======================================================================
    
       interface IMyInterface
       {
          string GiveSomethingBack();
       }
    
       //======================================================================
    
       class MyClass : IMyInterface
       {
    
          #region IMyInterface Members
          public string GiveSomethingBack()
          {
             return "Hello from MyClass";
          }
          #endregion
       }
    
       //======================================================================
    
       class MyOtherClass : IMyInterface
       {
          #region IMyInterface Members
          public string GiveSomethingBack()
          {
             return "Hello from MyOtherClass";
          }
          #endregion
       }
    }

    Na, hat's >>Klick<< gemacht? Immer dann wenn dynamisch verschiedene Klassen auf einen Typ referenziert werden müssen, kann das über ein Interface gelöst werden. List<T> erwartet einen ganz bestimmten Typ - und natürlich nur einen. Setzt man ein Interface ein (in diesem Fall IMyInterface), kann man Instanzen aller Klassen übergeben, die dieses Interface implementieren! Diese Implementierung sorgt nämlich dafür daß alle Objekte in so einer Klasse angelegt werden müssen, die ein Interface anbietet - und macht die Klassen somit kompatibel. Es ist wie ein Schlüssel/Schloß-Prinzip. Klassen stellen unterschiedliche Typen dar (class Auto ist nicht gleich class Person). Durch eine gemeinsame Interface-Implementierung erhalten sie eine SCHNITTSTELLE, die sie zumindest teilweise für andere gleich macht.
    Ja richtig, es ist nicht immer notwendig, daß alle Objekte in einer Klasse vom Interface abgeleitet sind (aber alle Objekte die das Interface anbietet!).
    Jetzt nicht verwirren lassen. Besser den vorigen Satz mehrmals lesen als einmal falsch verstehen!
    Wichtig ist, daß alle Objekte die für den gemeinsamen Zugriff erforderlich sind, auch existieren. Um bei den Klassen Auto und Person zu bleiben, könnte man z.B. ein Interface schreiben, daß ein Property "Name" beinhaltet. Nun kann man sich über eine einzige Funktion, die einen Typ dieses Interfaces erwartet, die Namen ausgeben lassen - egal ob es sich dabei um ein Auto handelt oder um eine Person. Zugleich kann die Klasse Auto aber noch andere Objekte enthalten (z.B. Fabrikat) als die Klasse Person (z.B. Geschlecht), ohne daß es deshalb zu Problemen kommt. Für die Funktion, welche die Namen über den Typ des Interfaces abfragt, sind diese zusätzlichen, inkompatiblen Objekte gar nicht sichtbar! Obendrein fungiert so ein Interface also auch noch wie ein Objektfilter.
    Cool wa?!

    May 19

    Extension Methods am Beispiel von Limit<> und LimitRange<>

    Wer diesen Artikel sucht, wird sich jetzt vielleicht wundern. Er ist aber nicht fort. Mittlerweile waren es nur vier Blogeinträge, die alle aufeinander aufbauten. Ich habe jetzt alles in diesem Artikel zusammengefasst.

    May 14

    Die Zukunft der Computerbedienung schon jetzt mit Microsoft Surface!

     

     surfacelogo

    Ich war schon vor Monaten über diesen Link gestolpert, kam aber erst jetzt auf die Idee, darüber etwas in mein Blog zu schreiben.
    Ich spare mir jeden weiteren Kommentar. Meine Worte könnten nicht ausdrücken was für ein Look & Feel das ist. Schaut euch das an!

    Ein Weltraumteleskop für Jedermann!

     

     wwt_icon1

    Google Earth hat es mit seinem Planetarium-Modus vorgemacht, Microsoft setzt noch einen drauf.
    Wer sich für Astronomie interessiert und wen Bilder ferner Gasnebel und Galaxien faszinieren, der sollte sich unbedingt das WorldWide Telescope ziehen (kostenlos)! Es besticht durch diverse Features die man bei Google Earth vergeblich sucht (z.B guided Tours).

    May 09

    Vergleichsverfahren, Fortsetzung

    Wie im Hauptartikel zu sehen war, gibt es verschiedene Verfahren, die im Prinzip aber irgendwie das gleiche machen. Ist es da nicht egal, ob man if/else benutzt oder eine der anderen Operatoren? Statt des ??-Operators kann man wohl auch gleich ? und : benutzen, der Compiler macht sowieso letzteres daraus! Allerdings finde ich die Syntax bei der ??-Variante menschenfreundlicher als das andere - naja, das ist Geschmacksache.

    Und was ist mit "if/else" im Vergleich zu "?/:" ?
    Ich denke, im Prinzip ist es nicht so wichtig, aber wie bei so vielen Dingen steckt der Unterschied im Detail.
    Dazu mal ein kleiner Test. Man schreibe folgenden Code und lasse ihn kompilieren:

    using System;
    
    namespace ILtest
    {
       class Program
       {
          static void Main()
          {
             int wert = 150;
             int ergebnis = wert > 100 ? 100 : wert;
             //
             if(wert > 100)
                ergebnis = 100;
             else
                ergebnis = wert;
          }
       }
    }

    Hier das disassemblierte Kompilat im Reflector:

    internal class Program
    {
        // Methods
        private static void Main()
        {
           int wert = 150;
           int ergebnis = (wert > 100) ? 100 : wert;
           if (wert > 100)
           {
              ergebnis = 100;
           }
           else
           {
              ergebnis = wert;
           }
        }
    }

    Der Compiler hat diese Struktur also beibehalten. Schauen wir uns nun den IL-Code (siehe Fußnote) an:

    //000008:       {
      IL_0000:  nop
    //000009:          int wert = 150;
      IL_0001:  ldc.i4     0x96
      IL_0006:  stloc.0
    //000010:          int ergebnis = wert > 100 ? 100 : wert;
      IL_0007:  ldloc.0
      IL_0008:  ldc.i4.s   100
      IL_000a:  bgt.s      IL_000f
      IL_000c:  ldloc.0
      IL_000d:  br.s       IL_0011
      IL_000f:  ldc.i4.s   100
      IL_0011:  stloc.1
    //000011:          //
    //000012:          if(wert > 100)
      IL_0012:  ldloc.0
      IL_0013:  ldc.i4.s   100
      IL_0015:  cgt
      IL_0017:  ldc.i4.0
      IL_0018:  ceq
      IL_001a:  stloc.2
      IL_001b:  ldloc.2
      IL_001c:  brtrue.s   IL_0023
    //000013:             ergebnis = 100;
      IL_001e:  ldc.i4.s   100
      IL_0020:  stloc.1
      IL_0021:  br.s       IL_0025
    //000014:          else
    //000015:             ergebnis = wert;
      IL_0023:  ldloc.0
      IL_0024:  stloc.1
    //000016:       }
      IL_0025:  ret
    } // end of method Program::Main

    Wie man sieht, ist die Prüfung mittels "?:" tatsächlich kürzer. In Zeiten von mehreren Gigabyte großem RAM ist das wohl trivial, aber man stelle sich die Abfrage einer Liste mit hunderttausend und mehr Einträgen vor. Das bringt wohl doch einen Geschwindigkeitsvorteil.

    Fußnote
    Was ist IL?
    IL steht für Intermediate Language. Der Compiler erzeugt nicht direkt ausführbaren Code, sondern eine Art Zwischenstufe dazu. Die entgültige Kompilierung geschieht zur Laufzeit.

    Wie kann man IL sichtbar machen?
    Dazu gibt es das Tool ildasm, das zum Windows SDK gehört. Es gibt verschiedene Versionen von diesem SDK. Geht dazu einfach auf die Microsoft-Seite und gebt "Windows SDK" in der Suche ein.

    May 08

    Vergleichsverfahren

    Keine Angst, wir gehen nicht vor Gericht. ;-)

    Ich möchte dem C#-Einsteiger hier etwas über verschiedene Verfahren
    für die Prüfung (Validierung) von Werten erzählen.
    C# stellt dafür verschiedene Werkzeuge zur Verfügung.
    Ich hab auch mal angefangen und weiß deshalb aus erster Hand, nämlich von mir ;-),
    daß dieser Umstand für etwas Verwirrung sorgen kann.
    Teilweise ist die Syntax nicht gerade menschenfreundlich,
    zum anderen Teil weiß man noch nicht einmal von deren Existenz.
    if/else
    Func<T, TResult> =>
    ? :
    ??
    Alles schon einmal gesehen und auch damit gearbeitet?
    Wenn ja, dann können Sie diesen Artikel jetzt verlassen und sich anderen Dingen widmen.
    Allen anderen wünsche ich viel Spaß beim Lesen und nachmachen. :-)
    Der Code ist selbsterklärend geschrieben.
    Bei weiteren Fragen, bitte Kommentar hinterlassen, oder mail an
    rainer_hilmer@hotmail.com.
    Hier nun eine Liste (mit Beispielen) aller (mir bekannten) Validierungswerkzeuge.
    (Kennt jemand noch mehr? Dann lasst mich bitte nicht dumm sterben!)

    Ausgenommen sind klassenspezifische Validierungsmethoden,
    weil diese den Rahmen dieses Artikels bei weitem sprengen würden.
    using System;
    
    namespace Vergleichsverfahren
    {
       class Program
       {
          static void Main()
          {
             Console.WriteLine("Vergleich über if/else");
             Console.WriteLine("Geprüft wird, ob eine Zahl groesser 0 ist.");
             Console.WriteLine("Gib eine Ganzzahl ein:");
             // Fehleingaben werden hier nicht behandelt!
             int number = int.Parse(Console.ReadLine());
             Console.WriteLine("Die Zahl ist {0}.", Comparator.IfMethod(number));
    
             //=====================================================================================
    
             Console.WriteLine("\n\nVergleich über ? :");
             Console.WriteLine("Geprüft wird, ob eine Zahl groesser 0 ist.");
             Console.WriteLine("Gib eine Ganzzahl ein:");
             // Fehleingaben werden hier nicht behandelt!
             number = int.Parse(Console.ReadLine());
             Console.WriteLine("Die Zahl ist {0}.", Comparator.QuestionMethod(number));
    
             //=====================================================================================
    
             Console.WriteLine("\n\nVergleich über ??" +
                "\nDieser Operator ist ein Sonderfall, weil er nur auf null prüft." +
                "\nIst ein Wert null," +
                "\nkann er mit diesem Verfahren gegen etwas anderes getauscht werden" +
                "\n?? wird also immer dann eingesetzt," +
                "\nwenn ein Empfänger-Objekt null nicht verarbeiten kann.");
             Console.WriteLine("\nEs wird ein Datenbankzugriff simuliert." +
             " Der User sucht einen bestimmten Datensatz." +
             " Das Produkt \"Eiscreme\" wird gefunden, etwas anderes nicht" +
             "\n(es wird null zurückgegeben)." +
             "\nNach welchem Produkt suchst du?");
             string productName = Console.ReadLine();
             // Die Empfängervariable kann null nicht verarbeiten!
             // Darum muss null in etwas anderes gewandelt werden.
             Guid result = Comparator.ValidateProduct(productName);
             if(result.Equals(Guid.Empty))
                Console.WriteLine("Datensatz wurde nicht gefunden.");
             else
                Console.WriteLine("Datensatz mit der ID {0} wurde gefunden.", result.ToString());
    
             //=====================================================================================
    
             Console.WriteLine("\n\nVergleich über Lambda-Expression," +
                "\nmit Hilfe von Func<T, TResult>");
             Console.WriteLine("Es wird ein Druckwächter simuliert." +
                "Ist der Druck groesser als 5, wird Alarm gegeben.");
             Console.WriteLine("Wie hoch soll der Druck sein?");
             // Fehleingaben werden hier nicht behandelt!
             int pressure = int.Parse(Console.ReadLine());
             bool pressureOK = Comparator.CheckPressureWithLambda(pressure);
             DisplayPressureValidationResult(pressureOK);
    
             //=======================================================================================
    
             Console.WriteLine("\n\nVergleich über logischen Operator");
             Console.WriteLine("Wieder wird ein Druckwächter simuliert," +
                "\nnur läuft der Vergleich diesmal über eine logische Operation.");
             Console.WriteLine("Wie hoch soll der Druck sein (s.o.)?");
             // Fehleingaben werden hier nicht behandelt!
             pressure = int.Parse(Console.ReadLine());
             pressureOK = Comparator.CheckPressureWithLogicalOperation(pressure);
             DisplayPressureValidationResult(pressureOK);
          }
    
          //=======================================================================================
    
          // Spätestens wenn ein und der selbe Codeblock mehrmals benutzt wird,
          // sollte man ihm eine eigene Methode gönnen.
          private static void DisplayPressureValidationResult(bool pressureOK)
          {
             if(pressureOK)
                Console.WriteLine("Der Druck ist im Limit.");
             else
                Console.WriteLine("ACHTUNG! Der Druck ist zu hoch!");
          }
       }
    
       //=====================================================================================
       //=====================================================================================
    
       static class Comparator
       {
          // Vergleicht durch If-Operator.      
          internal static string IfMethod(int number)
          {
             if(number > 0)
                return "groesser als 0";
             return "ist 0";
          }
    
          // Vergleicht durch ?-Operator.
          internal static string QuestionMethod(int number)
          {
             return number > 0 ? "groesser als 0" : " ist 0";
    } // Simuliert einen Datenbankzugriff. // Wurde das Produkt "Eiscreme" als Parameter übergeben, // gibt die Methode eine GUID zurück, anderenfalls null. // Man beachte daß der Rückgabetyp hier nullable ist. internal static Guid? GetProduct(string productName) { if(productName.Equals("Eiscreme")) return Guid.NewGuid(); return null; } // Testet mittels ??-Operator, ob ein gültiges Produkt eingegeben wurde. // Wenn gültig, dann wird die ID zurückgegeben, anderenfalls eine leere GUID
    //
    (was nicht das gleiche ist wie null!). // Man beachte daß der Rückgabetyp hier NICHT nullable ist! internal static Guid ValidateProduct(string productName) { // Der ??-Operator kann nur gegen null prüfen. return GetProduct(productName) ?? Guid.Empty; } // Testet über eine Lambda-Expression, ob ein Wert kleiner gleich 5 ist. // Der Func-Delegat ist Bestandteil von .net3.5 internal static bool CheckPressureWithLambda(int value) { Func<int, bool> myFunc = x => x <= 5; return myFunc(value); } // Leistet das Gleiche wie die Lambda-Version! internal static bool CheckPressureWithLogicalOperation(int value) { return value <= 5; } } }
    Jetzt wird es erst richtig interessant!
    Disassembliert man das Kompilat mit
    Reflector, offenbaren sich so einige "Geheimnisse".
    internal static class Comparator
    {
        // Methods
    Das war mal eine Lambda-Expression:
        internal static bool CheckPressureWithLambda(int value)
        {
            Func<int, bool> myFunc = delegate (int x) {
                return x <= 5;
            };
            return myFunc(value);
        }
    
        internal static bool CheckPressureWithLogicalOperation(int value)
        {
            return (value <= 5);
        }
    
        internal static Guid? GetProduct(string productName)
        {
            if (productName.Equals("Eiscreme"))
            {
                return new Guid?(Guid.NewGuid());
            }
            return null;
        }
    
        internal static string IfMethod(int number)
        {
            if (number > 0)
            {
                return "groesser als 0";
            }
            return "ist 0";
        }
    
        internal static string QuestionMethod(int number)
        {
            return ((number > 0) ? "groesser als 0" : " ist 0");
        }
    

    Der ??-Operator ist nicht mehr wiederzuerkennen:
        internal static Guid ValidateProduct(string productName)
        {
            Guid? CS$0$0001 = GetProduct(productName);
            return (CS$0$0001.HasValue ? CS$0$0001.GetValueOrDefault() : Guid.Empty);
        }
    }

    May 05

    Frühe und späte Initialisierung von Singletons

    Ein sehr interessanter Artikel, den sich jeder Entwickler mal durchlesen sollte (englisch):

    http://geekswithblogs.net/akraus1/articles/90803.aspx

    May 03

    DB-Concurrency Problem mit Hilfe von Timestamp-Feldern lösen

    Zufällig habe ich in Dan Wahlins Blog etwas sehr interessantes gefunden:

    since the SQL can use the TimeStamp field in the WHERE class to see if any concurrency issues have occurred.

    Quelle: <http://weblogs.asp.net/dwahlin/archive/2008/02/28/building-an-n-layer-asp-net-application-with-linq-lambdas-and-stored-procedures.aspx>
    (im Kommentar vom February 19, 2008 10:39 AM)

    Konkret:

    • Die Tabellen erhalten ein zusätzliches Timestamp-Feld, in das bei jedem Update der aktuelle Timestamp geschrieben wird.
    • Update-Methoden werden so erweitert, daß sie vor dem schreibenden DB-Zugriff erst noch einmal prüfen ob der Timestamp sich seit dem vorangegangenen Read geändert hat. Da es bei n-Tier Applikationen selbst zwischen diesen beiden Operationen schon zu Verzögerungen kommen kann, schlage ich vor, diese Überprüfung in einer SPROC zu machen. Näher am Geschehen geht nicht! :-)

    Versucht man nun einen Tupel upzudaten, während zwischendurch schon jemand anderer das Gleiche gemacht hat, kann reagiert werden.

    May 02

    Value Limiter über Extension Method

    Ich bastle gerade an einer Prozessüberwachung. Beim Auslesen der Prozessorlast fiel mir auf daß die Werte 100 übersteigen können (keine Ahnung warum). Da ich die Last über einen Progressbar grafisch darstellen will, mußte ich einen Limiter dazwischenschalten. Dieses Objekt kann einfach so erstellt werden:

    public int Limit(int value)
    {
       return value > 100 ? 100 : value;
    }

    Auf Deutsch: Ist value größer 100? Dann gib 100 zurück, sonst gib den Wert von value zurück.

    Da ich nebenbei auch mein eigenes Framework mit Objekten erstelle, die ich bei meiner Entwicklerarbeit immer wieder benötige,
    kam mir die Idee, so einen Limiter als generische Extension Method in mein Framework zu integrieren. Generisch deshalb weil ich diesen Limiter auf diese Weise gleich für alle Zahlentypen zur Verfügung habe. Es ist also ganz egal ob man mit int, long, decimal, float, double, short oder Single kommt, solange der Typ das IComparable<T>-Interface implementiert, hat er jetzt die Extension Method "Limit<>" im Gepäck.

    public static class Extensions
    {
       public static T Limit<T>(this T value, T maximum) where T : IComparable<T>
       {
          return value.CompareTo(maximum) < 1 ? value : maximum;
       }
    }

    Benutzt wird diese Methode dann wie folgt:

    image

    Der fertige Code (Cyrons.Extensions.dll wird referenziert):

    using Cyrons.Extensions;
    ...
    int testValue = 150;
    int limitedResult = testValue.Limit(100);
    // limitedResult wird 100 sein.

    Es geht aber noch besser. Wie wäre es, wenn ein Event Abonnenten über Grenzwertverletzungen informiert? Um die Sache rund zu machen, habe ich eine Überladung der Limit<>-Methode geschrieben, bei der man zusätzlich den Absender angeben kann. Somit kann ein Objekt, das sich mit LimitExceeded verbunden hat, jederzeit darüber informiert werden, wenn irgendwo in der Applikation ein Limit überschritten wurde! Und als ich schon einmal dabei war, die Erweiterung zu erweitern, da habe ich auch gleich noch eine weitere Extension Method gebastelt: LimitRange<>.
    Hier ist die finale Version.

       /// <summary>
       /// Stellt verschiedene Extension-Methoden zur Verfügung.    
       /// </summary>
       public static class Extensions
       {
          /// <summary>
          /// Tritt ein, wenn ein an Limit oder LimitRange übergebener Wert
          /// eine Grenzwertverletzung hervorgerufen hat.
          /// </summary>
          public static event EventHandler<LimitEventArgs> LimitExceeded;
    
          /// <summary>
          /// Verhindert daß eine Zahl einen Maximalwert überschreitet.
          /// </summary>
          /// <typeparam name="T">IComparable</typeparam>
          /// <param name="value">Wert eines Typs
          /// der das IComparable-Interface implementiert.</param>
          /// <param name="maximum">Der Maximalwert, den eine Zahl annehmen darf.</param>
          /// <returns>Der gefilterte Wert.</returns>
          /// <example>decimal result = testValue.Limit(100);</example>
          public static T Limit<T>(this T value, T maximum)
             where T : IComparable<T>
          {
             return value.CompareTo(maximum) < 1 ? value : maximum;
          }
    
          /// <summary>
          /// Verhindert daß eine Zahl einen Maximalwert überschreitet.
          /// </summary>
          /// <typeparam name="T"><see cref="IComparable"/></typeparam>
          /// <param name="value">Zu prüfender Wert</param>
          /// <param name="maximum">Der Maximalwert, den eine Zahl annehmen darf.</param>
          /// <param name="invoker">Die Klasse, die Limit aufgerufen hat
          /// (sollte "this" sein).</param>
          /// <returns>Der gefilterte Wert.</returns>
          ///<example>decimal result = testValue.Limit(100, this);</example>
          public static T Limit<T>(this T value, T maximum, object invoker)
             where T : IComparable<T>
          {
             if(value.CompareTo(maximum) < 1)
                return value;
             else
             {
                if(LimitExceeded != null)
                {
                   LimitEventArgs e =
                      new LimitEventArgs((IComparable)value, (IComparable)maximum);
                   LimitExceeded(invoker, e);
                }
                return maximum;
             }
          }
    
          /// <summary>
          /// Verhindert daß eine Zahl ein Minimum unterschreitet,
          /// oder ein Maximum überschreitet.
          /// </summary>
          /// <typeparam name="T"><see cref="IComparable"/></typeparam>
          /// <param name="value">Zu prüfender Wert</param>
          /// <param name="minimum">Das Minimum,
          /// welches eine Zahl nicht unterschreiten darf.</param>
          /// <param name="maximum">Das Maximum,
          /// welches eine Zahl nicht überschreiten darf.</param>
          /// <returns>Der gefilterte Wert.</returns>
          public static T LimitRange<T>(this T value, T minimum, T maximum)
             where T : IComparable<T>
          {
             if(value.CompareTo(minimum) < 0)
                return minimum;
             else if(value.CompareTo(maximum) > 0)
                return maximum;
             else
                return value;
          }
    
          /// <summary>
          /// Verhindert daß eine Zahl ein Minimum unterschreitet,
          /// oder ein Maximum überschreitet.
          /// </summary>
          /// <typeparam name="T"><see cref="IComparable"/></typeparam>
          /// <param name="value">Zu prüfender Wert</param>
          /// <param name="minimum">Das Minimum,
          /// welches eine Zahl nicht unterschreiten darf.</param>
          /// <param name="maximum">Das Maximum,
          /// welches eine Zahl nicht überschreiten darf.</param>
          /// <param name="invoker">Die Klasse, die LimitRange aufgerufen hat
          /// (sollte "this" sein).</param>
          /// <returns>Der gefilterte Wert.</returns>
          public static T LimitRange<T>(this T value, T minimum, T maximum, object invoker)
             where T : IComparable<T>
          {
             if(value.CompareTo(minimum) < 0)
             {
                if(LimitExceeded != null)
                {
                   LimitEventArgs e = new LimitEventArgs(
                      (IComparable)value, (IComparable)minimum, (IComparable)maximum);
                   LimitExceeded(invoker, e);
                }
                return minimum;
             }
             else if(value.CompareTo(maximum) > 0)
             {
                if(LimitExceeded != null)
                {
                   LimitEventArgs e = new LimitEventArgs(
                      (IComparable)value, (IComparable)minimum, (IComparable)maximum);
                   LimitExceeded(invoker, e);
                }
                return maximum;
             }
             else
                return value;
          }
          // Weitere Extension-Methods...
       }
     
       /// <summary>
       /// Stellt Daten für das LimitExceeded-Event zur Verfügung.
       /// </summary>
       public sealed class LimitEventArgs : EventArgs
       {
          StackTrace st;
    
          /// <summary>
          /// Initialisiert eine neue Instanz der <see cref="LimitEventArgs"/> Klasse.
          /// </summary>
          /// /// <param name="value">The value.</param>      
          /// <param name="maximum">The maximum.</param>
          public LimitEventArgs(IComparable value, IComparable maximum)
          {
             st = new StackTrace(true);
             /* Index 0: ctor von LimitEventArgs
              * Index 1: Eventauslöser (ist Limit<T>)
              * Index 2: Die Methode die Limit<T> verwendet. */
             this.InvokerMethod = st.GetFrame(2).GetMethod();
             this.Maximum = maximum;
             this.Value = value;
          }
    
          /// <summary>
          /// Initialisiert eine neue Instanz der <see cref="LimitEventArgs"/> Klasse.
          /// </summary>
          /// <param name="value">The value.</param>
          /// <param name="minimum">The minimum.</param>
          /// <param name="maximum">The maximum.</param>
          public LimitEventArgs(IComparable value, IComparable minimum, IComparable maximum)
          {
             st = new StackTrace(true);
             this.InvokerMethod = st.GetFrame(2).GetMethod();
             this.Maximum = maximum;
             this.Minimum = minimum;
             this.Value = value;
          }
    
          /// <summary>
          /// Gibt die Methode zurück,
          /// durch die das LimitExceeded-Event ausgelöst wurde.
          /// </summary>      
          public System.Reflection.MethodBase InvokerMethod { get; private set; }
    
          /// <summary>
          /// Ruft das an die Limit-Methode übergebene Maximum ab.
          /// </summary>
          public IComparable Maximum { get; private set; }
    
          /// <summary>
          /// Ruft das an die Limit-Methode übergebene Minimum ab.
          /// </summary>
          public IComparable Minimum { get; private set; }
    
          /// <summary>
          /// Ruft den an die Limit-Methode übergebenen Wert ab.
          /// </summary>     
          public IComparable Value { get; private set; }
       }
     LimitundLimitRange11  

    Dazu wieder ein Demo

    class ClassA
       {
          static void Main()
          {
             ClassB anotherLimitBreaker = new ClassB();
             ClassC rangeBreaker = new ClassC();
             ClassD observer = new ClassD();
             observer.AttachToLimitExceededEvent();
             int testValue = 150;
             // Die Version ohne invoker und damit ohne Event:
             int limitedResult = testValue.Limit(100);
             // limitedResult wird 100 sein.         
             Console.WriteLine("limitedResult ist {0}\r\n", limitedResult.ToString());
    
             anotherLimitBreaker.ExceedLimit();
    
             //Alles OK
             rangeBreaker.ExceedRange(1, 0, 100);
             // Minimum unterschritten
             rangeBreaker.ExceedRange(-1, 0, 100);
             // Maximum überschritten
             rangeBreaker.ExceedRange(110, 0, 100);
    
             // Verhindert das selbsttätige Schließen des Konsolenfensters.
             Console.WriteLine("\nPress any key to terminate the program.");
             Console.ReadKey();
          }
       }
    
       //======================================================================
    
       // Ein weiterer Limit-Brecher
       class ClassB
       {
          public void ExceedLimit()
          {
             double testValue = 3.141592654;
             // Die Version mit invoker und dadurch mit Event:
             double limitedResult = testValue.Limit(2.0, this);
          }
       }
    
       //======================================================================
    
       // A range breaker
       class ClassC
       {
          public void ExceedRange(int value, int minimum, int maximum)
          {
             int result = value.LimitRange(minimum, maximum, this);
          }
       }
    
       //======================================================================
    
       // Observer
       class ClassD
       {
          public void AttachToLimitExceededEvent()
          {
             Extensions.LimitExceeded += new EventHandler<LimitEventArgs>(LimiterProbe);
          }
    
          protected void LimiterProbe(object sender, LimitEventArgs e)
          {
             Console.WriteLine(
                "In {0}.{1} gab es eine Grenzwertverletzung.",
                sender.ToString(), e.InvokerMethod.ToString());
             Console.WriteLine("Die Daten:");
             Console.WriteLine("Der Wert: {0}", e.Value);
             if(e.Minimum != null)
                Console.WriteLine("Das Minimum: {0}", e.Minimum);
             Console.WriteLine("Das Maximum: {0}", e.Maximum);
    
             // Wenn man Value, Minimum und Maximum castet, kann man damit auch rechnen:
             try
             {
                if(Convert.ToDecimal(e.Value) > Convert.ToDecimal(e.Maximum))
                   Console.WriteLine("Der Wert überstieg das Maximum um {0}%\r\n",
                      Math.Round((((Convert.ToDecimal(e.Value)
                      / (Convert.ToDecimal(e.Maximum)) * 100) - 100)), 1));
                else
                   Console.WriteLine();
             }
             catch(Exception)
             {
                Console.WriteLine("Berechnung konnte nicht durchgeführt werden!");
             }
             // Erst wenn das Abo nicht mehr gebraucht wird:
             //DetachStaticEvents();
          }
    
          /// <summary>
          /// Löst Verbindungen für statische Events auf, um Speicherlecks zu verhindern.
          /// </summary>
          protected void DetachStaticEvents()
          {
             /* Laut MSDN ergeben sich Speicherlecks,
              * wenn man Verbindungen zu statischen Events nicht wieder auflöst
              * (siehe ThreadException Event im MSDN). */
             Extensions.LimitExceeded -= new EventHandler<LimitEventArgs>(LimiterProbe);
          }
       }

    Das (oder die?) Demo erzeugt folgenden Output:

    limitedResult ist 100

    In ExtendingLimits.ClassB.Void ExceedLimit() gab es eine Grenzwertverletzung.
    Die Daten:
    Der Wert: 3,141592654
    Das Maximum: 2
    Der Wert überstieg das Maximum um 57,1%

    In ExtendingLimits.ClassC.Void ExceedRange(Int32, Int32, Int32) gab es eine Grenzwertverletzung.
    Die Daten:
    Der Wert: -1
    Das Minimum: 0
    Das Maximum: 100

    In ExtendingLimits.ClassC.Void ExceedRange(Int32, Int32, Int32) gab es eine Grenzwertverletzung.
    Die Daten:
    Der Wert: 110
    Das Minimum: 0
    Das Maximum: 100
    Der Wert überstieg das Maximum um 10,0%

    Press any key to terminate the program.