.
Anmeldung | Registrieren | Hilfe | Posteingang
Suchen
Home Foren News Member Offers Termine Developer Blogs Knowledge Base

Navigation

Navigationslinks überspringen.
Knowledge Base reduzierenKnowledge Base
Tutorials reduzierenTutorials
Webentwicklung
Cliententwicklung
Datenbankentwicklung
IT Professional
Sharepoint
Sprachspezifisch reduzierenSprachspezifisch
C#
Visual Basic
C++
XAML
SQL
JavaScript
Erfahrungsberichte reduzierenErfahrungsberichte
Entwicklersoftware
Bücher
FAQ Grundlagen

Verknüpfungen

  • Knowledge Base durchsuchen
  • Hilfe zur Knowledge Base
  • RSS Feed
  • Twitter

Extension Methods (C#3 Spracherweiterung) am Beispiel eines Value Limiters

Was sind Extension Methods?

Mit C# 3 hat Microsoft die Möglichkeit geschaffen, Klassen, Structs und Typen (ist im Prinzip das Gleiche) in anderen Assemblies nachträglich in der Funktionalität zu erweitern.
Extensions sind einfach zu schreiben. Man muss dafür nur eine handvoll Regeln beachten.

  • Alle Extension Methods müssen in einer (oder auch mehreren) statischen Klasse(n) stehen (static keyword).
  • Diese Klasse darf public oder internal sein.
  • Die Extension Methods selber müssen ebenfalls statisch sein.
  • Der erste Parameter einer Extension Method muss der Typ sein, der erweitert werden soll. Dieser Parameter wird bei der späteren Verwendung einer Extension nicht sichtbar sein. Er dient dem Compiler als Zuweisung zu dem gewünschten Typ.
  • Diesem Parameter wird das Schlüsselwort “this” voran gestellt.
  • Der Rückgabetyp einer Erweiterungsmethode muss dem Typ entsprechen, der erweitert werden soll. Eine Erweiterung von DateTime mit Rückgabe eines Strings ist also nicht möglich.

Hier ein Beispiel:

public static class MyExtensions
{
public static int Limit_to_hundred(this int @value)
{
if(@value > 100)
return 100;
return @value;
}
}

Diese Extension erweitert den Typ int um die Methode Limit_to_hundred. In der Anwendung sieht das dann so aus:

static void Main()
{
int someValue = 250;
int limitedValue = someValue.Limit_to_hundred();
Console.WriteLine(limitedValue); // Ergebnis: 100
}

Ist nicht so schwer, oder? Smiley
Jetzt zu einem konkreten Fall.

Ich bastle gerade an einer Prozessüberwachung. Beim Auslesen der Prozessorlast fiel mir auf daß die Werte 100 übersteigen können. 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 (Die Extensions-Klasse ist Bestandteil von Cyrons.Extensions.dll):

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.

von Rainer Hilmer, 01.07.2008 zugeordnet zu C# , Sprachenspezifisch .

Kommentare

Ich finde die Verwendung von Extension Methods nur sinnvoll, wenn ich nicht auf andere konvetionelle Art und Weise meine Lösung implementieren kann. Extension Methods sind meiner Meinung nach"gefährlich" und sollten nur mit bedacht eingesetzt werden. Warum keine Generische Klasse die als Parameter den Typ bekommt. Bsp.: Limiter<int> myValue; Ein Property Value dazu und die Funktion ist fertig. Diese Lösung würde ich nur in erwähnung ziehen, wenn ich eine 3rd Party Komponente, Closed Source vor mir habe, versiegelte Klassen, an dessen Code ich nichts ändern kann.
von Rainer Schuster, 01.07.2008.

Das ist ein Artikel für den Wettbewerb "Neue Sprachfeatures in C#3". Extension Methods gehören nun einmal dazu.
von Cyron, 14.07.2008.

Meiner Meinung nach sind die "Extension Methods" eine sehr hilfreiche Erweiterung und eine der genialsten Neuerungen in C# 3.0. Sind sind Typsicher und nach einem Verweis überall verwendbar. Wie wurde früher mit eigenen Methoden umgegangen? Man hat sich eine Helper-Klasse geschrieben und diese dann verwendet. Dadurch war aber der Umgang bei weitem nicht so komfortabel wie mit den Erweiterungs-Methoden.
Einwände wie z.B.: "sie sind gefährlich wenn gleiche Namen in unterschiedlichen Namensräumen verwendet werden" lasse ich so nicht gelten. Das gleiche Problem tritt mit jedem Typ im Framework auf, der in mehr als einem Namensraum verwendet wird. Siehe z.B.: die Timer-Klasse. Sie kommt in 3 verschiedenen Namensräumen vor.
@ Rainer Schuster: Dein Ansatz ist der selbe wie früher. Eine Klasse die nicht direkt verwendet werden kann. Mit den Erweiterungs-Methoden geht das wirklich wunderbar. Schau dir allein den Namensraum System.Data an. z.B.: DataRow.Field<T> gibt dir Zugriff auf jedes einzelne Feld einer DataRow auf jede erdenkliche Art und Weiße. Vieleicht nimmst du die Möglichkeiten der Erweiterungs-Methode einmal näher in Augeschein. Es wird dir gefallen.
von klaus_b, 15.07.2008.

Eigener Kommentar

Sie müssen angemeldet sein, um ein Kommentar zu erstellen.
  • Schwierigkeit: Fortgeschrittene
  • Views: 2055
  • Zur Druckversion
  • Artikel von Rainer Hilmer

Kick it on dotnet-kicks.de

Artikel

Autor

Kick it!

Wenn ihnen dieser Artikel gefällt, bitte "kicken" sie ihn.

WPF Forum | ASP.NET Forum | ASP.NET MVC Forum | Silverlight Forum | Windows Phone 7 Forum | SharePoint Forum | Dotnet Jobs | Dotnet Termine | Developer Blogs | Dotnet News

Das Team | Regeln | Impressum