Selbst Hand angelegt

Sinnvolle Erweiterung für .NET mit den C# Extensions
Kommentare

Seit .NET 3.0 gibt es in C# die Möglichkeit, Klassen mit Extensions zu erweitern. Bekanntestes Beispiel sind wohl die LINQ-Abfrageoperatoren. Sie erweitern die IEnumerables durch verschiedene Abfragemethoden. Doch welche Extensions sind im täglichen Einsatz für uns wirklich sinnvoll und wie sollten wir sie am besten einsetzen? Wie können Extensions unseren Programmiereralltag vereinfachen?

Microsofts C# Extensions gibt es nun schon eine ganze Weile und auf verschiedene Weise haben wir sie alle schon einmal eingesetzt oder zumindest genutzt, denn Microsoft selbst setzt in vielen Bereichen darauf. Die Idee dahinter ist einfach: Extensions sollen uns die Möglichkeit geben, selbst Hand anzulegen, wenn eine bestimmte Funktionalität gefragt ist, aber noch nicht existiert. Sie sollen die Aufrufe von Quelltext vereinfachen und unsere Anwendungen zu einer höheren Wartbarkeit führen. Das soll dann noch unter der Prämisse entstehen, die ursprüngliche Komponente nicht verändern zu können, keine davon abgeleitete Komponente erstellen und die ursprüngliche Komponente dennoch wie gehabt nutzen zu wollen. Steigen wir mit dem Beispiel aus Listing 1 in das Thema ein.

// String-Variable mit mehreren Leerzeichen
string text = "        ";

// Ohne Extension
if(!string.IsNullOrEmpty(text) && text.Trim() != "")
{
  // Tue etwas
}

// Mit Extension vor .NET 4.0
if(!text.IsNullOrWhiteSpace())
{
  // Tue etwas
}

// Ohne Extension seit .NET 4.0
if(!string.IsNullOrWhiteSpace(text))
{
  // Tue etwas
}

Noch immer kommt es häufig vor, dass wir Objekte vom Typ String haben, die ausschließlich aus Leerzeichen bestehende Werte enthalten. Das kann zum Beispiel daran liegen, dass dieser Wert aus einer Datenbank mit dem Werttyp char und einer festen Länge von acht Zeichen kommt, oder auch von einem Formular auf einer Webseite, das mit falschen Eingabewerten übermittelt wurde.

Eine sichere Behandlung des Inhalts der Variable muss hier zuerst NULL ausschließen, um Fehler bei der weiteren Überprüfung zu verhindern. Erst im zweiten Schritt kann die eigentliche Abfrage durchgeführt werden, da der Aufruf von Trim() vom String-Objekt nur dann gelingt, wenn das Objekt nicht NULL ist. Durch die Dopplung der Abfragen auf eine Variable wird der Quelltext gleich unübersichtlicher, was gerade bei mehreren Überprüfungen in einem If-Block zu schlecht lesbarem Code führt. Eine Extension des String-Objekts bietet hier eine gute Abhilfe, da es uns die Abfrage aller wichtigen Werte von einem einzelnen Methodenaufruf der Variable selbst ermöglicht. Seit .NET 4.0 gibt es von diesem Beispiel eine eigene Methode, die Teil des statischen Bereichs der String-Klasse ist. Und selbst dieser Aufruf wird durch eine Extension les- und vor allem auch schreibbarer.

Extensions ermöglichen uns also nicht nur, Quelltext lesbarer und wartbarer zu gestalten, sondern auch, ihn schreibbarer zu machen. Wir alle kennen Momente, in denen wir mehrfach „um die Ecke“ denken müssen und uns beim Programmieren die Finger verknoten, obwohl es viel einfacher gewesen wäre, wenn die gewünschte Funktionalität als einfache Methode der gerade verwendeten Klasse existieren würde. Extensions bieten uns auch oder gerade in diesen Fällen eine deutliche Verbesserung des .NET Frameworks.

In diesem Artikel stelle ich viele sinnvolle Möglichkeiten vor, die in beinahe jedem Projekt zum Einsatz kommen können und die Arbeit in vielen Bereichen ungemein erleichtern.

Die wichtigsten Extensions

Beginnen wir mit den grundlegendsten Extensions, den Erweiterungen der Werttypen wie string, int oder auch byte. Im erweiterten Kreis dazu zählt auch das DateTime-Objekt. Dazu die wichtigste Frage vorweg: Was wollen wir überhaupt erreichen? Die Codequalität und Übersichtlichkeit heben? Oder uns unsere Arbeit erleichtern? Als Antwort darauf gibt es nur eine Option: all das zusammen.

Beginnen wir mit dem String-Objekt. Hier gibt es so viele Einsatzgebiete und Variationen, dass wir zuallererst herausfinden müssen, welche Bereiche wir überhaupt erweitern wollen. Ich habe mich dabei auf die folgenden drei grundlegenden Bereiche festgelegt: Validierung, Konvertierung und Verarbeitung

String-Werte sind nicht nur der häufigste Datentyp, sondern auch der variabelste. Er kann beinahe jede Form von Daten enthalten. Seien es Zahlen, boolesche Werte, verschlüsselte und unverschlüsselte Texte oder auch nur einfache E-Mail-Adressen. Dadurch entstehen aber auch Fehlerquellen und diese wollen wir verhindern. Daher gilt es, Inhalte zu überprüfen, sie zu validieren.

Am häufigsten wird der Test auf numerische Zahlen und leere, nicht initialisierte Strings benötigt. Die Methode IsNumeric prüft, ob der Inhalt des gegebenen Strings eine Zahl ist. Die Methode IsNullOrWhiteSpace habe ich in der Einleitung schon angesprochen, sie prüft auf NULL und auf leere oder leer erscheinende Inhalte.

Sehr häufig kommen auch boolesche Werte vor, gerade da heute Inhalte aus verschiedenen Quellen zusammengetragen werden und jeder etwas anderes unter True und False versteht. Sei es 1/0, Ja/Nein, Valid/Invalid oder auch nur ein kleingeschriebenes true. Die Methode IsExpressionTrue versucht hier die gängigsten Variationen anzuwenden und den Inhalt darauf zu überprüfen. Als weitere häufig benötigte Validierung steht die IsEmail-Methode zur Verfügung (Listing 2).

public static bool IsNumeric(this string instance)
{
  Regex pattern = new Regex(@"^[0-9,.]+$");
  return pattern.IsMatch(instance.Trim());
}

public static bool IsNullOrWhiteSpace(this string instance)
{
  // Eigene Implementierung verwenden, um Kompatibilität zu früheren Versionen zu behalten
  if(instance != null)
  {
    for(int i = 0; i < instance.Length; i++)
    {
      if(instance[i] != ' ')
      {
        return false;
      }
    }
  }
  
  return true;
}

public static bool IsExpressionTrue(this string instance)
{
  // NULL gilt selbstverständlich als False
  if(instance == null)
  {
    Return false;
  }
  
  // Der String muss getrimmt werden, da "true" ebenfalls als True erkannt werden soll
  string expression = instance.Trim().ToLower(CultureInfo.CurrentCulture);
  
  // first check if the string is empty
  if(expression.IsNullOrWhiteSpace())
  {
    return false;
  }
  
  // Erst auf Zahlen überprüfen, da positive Zahlen immer als True gelten
  if(expression.IsNumeric())
  {
    if(expression.Contains(",") || expression.Contains("."))
    {
      double expD = expression.ToDouble();
      
      if(expD > 0)
      {
        return true;
      }
    }
    else
    {
      int expI = expression.ToInt32();
      
      if(expI > 0)
      {
        return true;
      }
    }
  }
  else
  {
    // Alle möglichen Variationen von True festlegen
    List trueExpressions = new List() {
      "true", "wahr", "richtig", "korrekt",
      "valid", "correct", "accurate", "proper",
      "respectable", "positiv", "positive",
      "100pro", "100%", "yes", "ja",
      "si", "ok", "legal", "legitime", "legitim"
    };
    
    // Alle möglichen Variationen von False festlegen
    List falseExpressions = new List() {
      "false", "unwahr", "nicht wahr", "falsch", "inkorrekt",
      "incorrect", "inaccurate", "illegitimate", "fake",
      "invalid", "negativ", "negative", "nein",
      "no", "non", "not", "illegitime", "illegitim"
    };
    
    if(trueExpressions.Contains(expression))
    {
      return true;
    }
    else if(falseExpressions.Contains(expression))
    {
      return false;
    }
  }
  
  return false;
}

public static bool IsEmail(this string instance)
{
  string strRegex = @"^([a-zA-Z0-9_-.]+)@(([[0-9]{1,3}"
    + @".[0-9]{1,3}.[0-9]{1,3}.)|(([a-zA-Z0-9-]+"
    + @".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(]?)$";
  
  Regex pattern = new Regex(strRegex);
  
  if(pattern.IsMatch(instance))
  {
    return true;
  }
  else
  {
    return false;
  }
}

[ header = Seite 2: Collections erweitern ]

Strings müssen aber nicht nur validiert, sondern auch sehr häufig konvertiert werden. Zahlen sind hier ein beliebtes Ziel. Zwar bietet .NET die große Convert-Klasse, aber ein Aufruf „1“.ToInt32() macht den Quelltext einfach les- und schreibbarer. Die gängigsten Methoden sind in dem hier aufgeführten Beispiel vertreten. Dazu gehören neben den typischen numerischen Konvertierungen auch eine ToEnum-Methode, die die Umwandlung zum beschriebenen Enum-Wert vereinfachen soll und Methoden zum Konvertieren in ByteArrays, die gerade für Verschlüsselungen des Öfteren benötigt werden (Listing 3).

// ToInt32 steht stellvertretend für alle numerischen Methoden,
// sie kann auf alle Zahlentypen wie double, decimal oder long angewendet werden
public static int ToInt32(this string instance)
{
  int result = 0;
  
  if(int.TryParse(instance, out result))
  {
    return result;
  }
  else
  {
    return default(int);
  }
}

public static bool ToBoolean(this string instance)
{
  bool result = false;
  
  // Wenn der String exakt den von .NET erwarteten Werten entspricht,
  // kann die Parse-Methode verwendet werden, ansonsten
  // wird die IsExpressionTrue-Methode angewendet
  if((instance.Equals("True", StringComparison.OrdinalIgnoreCase)
  || instance.Equals("False", StringComparison.OrdinalIgnoreCase))
  && bool.TryParse(instance, out result))
  {
    return result;
  }
  
  return instance.IsExpressionTrue();
}

// ByteArray per BlockCopy oder über die Ascii-Kodierung
public static byte[] ToAsciiByteArray(this string instance)
{
  System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
  return enc.GetBytes(instance);
}

public static byte[] ToByteArray(this string instance)
{
  byte[] bytes = new byte[instance.Length * sizeof(char)];
  System.Buffer.BlockCopy(instance.ToCharArray(), 0, bytes, 0, bytes.Length);
  return bytes;
}

public static T ToEnum(this string instance)
{
  try
  {
    return (T)Enum.Parse(typeof(T), instance);
  }
  catch(Exception ex)
  {
    return default(T);
  }
}

Der dritte und ebenfalls wichtige Bereich behandelt die Verarbeitungsmethoden. Hier zählen die Split– und die Format-Methode zu den am häufigsten genutzten. Beide sind aber gleichermaßen kompliziert und programmiererunfreundlich zu verwenden, weshalb ich sie gerne mit einer Extension vereinfache. Besonders die Format-Methode wird manchmal unübersichtlich und unleserlich, dabei ist die Umsetzung als String-Extension vergleichsweise einfach. Mit den Extensions wird aus einem Aufruf von Split mit .Split(new string[] { „,“ }) ein simples .Split(„,“) und aus dem Aufruf von string.Format(„Hallo {0}!“, „Welt“) ein tastaturfreundliches und logischeres „Hallo {0}!“.FormatIt(„Welt“) (Listing 4).

public static List Split(this string instance, string seperator)
{
  return instance.Split(seperator, int.MaxValue, StringSplitOptions.None);
}

public static List Split(this string instance, string seperator, int count, StringSplitOptions options)
{
  if(count == int.MaxValue)
  {
    return new List(instance.Split(new string[] { seperator }, options));
  }
  else
  {
    return new List(instance.Split(new string[] { seperator }, count, options));
  }
}

public static string FormatIt(string format, object arg0)
{
  return string.Format(format, arg0);
}

public static string FormatIt(this string format, params object[] parameters)
{
  return string.Format(format, parameters);
}

Neben dem String-Objekt möchte ich noch kurz auf die byte und die DateTime Extensions als grundlegende Erweiterungen eingehen. Als Rückkonvertierung eines ByteArrays in einen String habe ich immer auch die ToString– und die ToAsciiString-Methode als Extension für das byte-Objekt implementiert. Sobald in einem Projekt in irgendeiner Form Verschlüsselung zum Einsatz kommt oder man binäre Dateien schreiben möchte, benötigt man beide Seiten der Konvertierung. Im selben Zusammenhang steht auch die Konvertierung eines ByteArrays zu einem Stream (Listing 5).

public static string ToAsciiString(this byte[] instance)
{
  ASCIIEncoding enc = new ASCIIEncoding();
  return enc.GetString(instance);
}

public static string ToString(this byte[] instance)
{
  char[] chars = new char[instance.Length / sizeof(char)];
  System.Buffer.BlockCopy(instance, 0, chars, 0, instance.Length);
  return new string(chars);
}

public static Stream ToStream(this byte[] instance)
{
  MemoryStream memStream = new MemoryStream(instance);
  return memStream;
}

Das DateTime-Objekt bietet, wie auch schon das String-Objekt, eine Vielzahl an Erweiterungsmöglichkeiten. Das fängt bei einer einfachen NextDay– und PreviousDay-Methode an und hört auch bei einer AddBusinesDays-Methode (Listing 6) nicht auf. Da die Möglichkeiten zu zahlreich sind, erwähne ich hier nur diese drei Methoden. Etliche weitere Möglichkeiten können Sie meinem unten angesprochenen Beispielprojekt entnehmen.

public static DateTime NextDay(this DateTime start)
{
  return start + 1.Days();
}

public static DateTime PreviousDay(this DateTime start)
{
  return start - 1.Days();
}

public static DateTime AddBusinessDays(this DateTime current, int days)
{
  var sign = Math.Sign(days);
  var unsignedDays = Math.Abs(days);
  for(var i = 0; i < unsignedDays; i++)
  {
    do
    {
      current = current.AddDays(sign);
    }
    while(current.DayOfWeek == DayOfWeek.Saturday ||
    current.DayOfWeek == DayOfWeek.Sunday);
  }
  return current;
}

Damit sind die absoluten Basics im Grunde genommen abgeschlossen. Es gibt zahlreiche weitere Möglichkeiten, der Fantasie sind hierbei keine Grenzen gesetzt. Einziges Ziel ist und bleibt aber, die Arbeit in kleinen wie großen Projekten weiter zu vereinfachen.

Als Microsoft die üblichen Getter/Setter Properties durch die einfachen Auto Properties erweiterte, wurde unsere Arbeit noch effektiver. Der Unterschied zwischen den beiden ist zwar nicht riesig, aber doch ausreichend, um die Arbeit spürbar zu verbessern. Ähnliches erreichen wir durch die angesprochenen Extensions. Um viel zu erreichen, sind keine komplizierten Formeln oder Tricks von Nöten. Wir erweitern lediglich die vorhandenen Klassen um einige wenige, aber doch wichtige Funktionalitäten. Gerade die als unwichtig erscheinenden Dinge wie die Konvertierung von Datenwerten oder die Validierung von Inhalten sind das Gros unserer Arbeit und sollten gerade deshalb verbessert werden.

Collections erweitern

Eine der eher seltenen, aber performantesten Listen ist die LinkedList. Gerade bei großen Datenmengen und vielen Schleifendurchläufen ist diese Liste unschlagbar und übertrifft auch das Array an Performance. Leider fehlen diesem Collection-Typ ein paar wichtige Zugriffsmethoden, was die Nutzung doch eher erschwert. Abfragen von Listeneinträgen über den Index (der ja nicht existiert) oder das Hinzufügen von mehreren Einträgen mittels AddRange sind so nicht möglich. Mit ein wenig Aufwand sind beide Varianten aber doch als Teil der LinkedList verfügbar (Listing 7).

public static void AddRange(this LinkedList instance, IEnumerable list)
{
  foreach(T item in list)
  {
    instance.AddLast(item);
  }
}

public static void AddRange(this LinkedList instance, LinkedList list)
{
  foreach(T item in list)
  {
    instance.AddLast(item);
  }
}

public static void ToIndex(this LinkedList instance, int index, T value)
{
  instance.AddAfter(instance.GetNodeFromIndex(index), value);
}

public static void Add(this LinkedList instance, T item)
{
  instance.AddLast(item);
}

public static LinkedListNode GetNodeFromIndex(this LinkedList instance, int index)
{
  if(index == 0)
  {
    return instance.First;
  }
  else
  {
    int count = 0;
    
    if(index > Math.Round(Convert.ToDouble(instance.Count) / 2D, MidpointRounding.AwayFromZero))
    {
      count = instance.Count;
      index++;
      
      for(LinkedListNode node = instance.Last; node != null; )
      {
        if(index == count)
        {
          return node;
        }
        else
        {
          node = node.Previous;
          count--;
        }
      }
    }
    else
    {
      for(LinkedListNode node = instance.First; node != null; )
      {
        if(index == count)
        {
          return node;
        }
        else
        {
          node = node.Next;
          count++;
        }
      }
    }
  }
  
  return null;
}
public static T FromIndex(this LinkedList instance, int index)
{
  LinkedListNode node = instance.GetNodeFromIndex(index);
  
  if(node != null)
  {
    return node.Value;
  }
  else
  {
    return default(T);
  }
}

Die wichtige Funktionalität AddRange fehlt nicht nur bei der LinkedList, sondern auch dem Dictionary. Sie ist schnell und einfach implementiert, was uns aber viele Arbeitsschritte vereinfacht (Listing 8).

public static void AddRange(this Dictionary instance, Dictionary list)
{
  foreach(KeyValuePair item in list)
  {
    instance.Add(item.Key, item.Value);
  }
}

public static void RemoveRange(this Dictionary instance, Dictionary list)
{
  foreach(KeyValuePair item in list)
  {
    instance.Remove(item.Key);
  }
}

Als weitere praktische Erweiterung stelle ich hier noch die Grap-Methode der normalen Liste vor (Listing 9). Sie implementiert den recht häufigen Anwendungsfall, bei dem aus einer Liste ein Eintrag herausgesucht und danach direkt entfernt werden soll. Auch hier ist die Umsetzung einfach, aber effektiv.

public static T Grap(this List instance, int index)
{
  try
  {
    if(index < instance.Count)
    {
      T item = instance[index];
      instance.Remove(item);
      return item;
    }
    else
    {
      return default(T);
    }
  }
  catch
  {
    // Hier sieht man nochmal die string.Format Extension
    // der String-Klasse im Einsatz
    throw new IndexOutOfRangeException(
      "The index {0} is out of range.".FormatIt(index)
    );
  }
}

Generische Listen gehören seit ihrer Einführung in .NET zu den praktischsten Objekten und haben uns mit ihrer einfachen Umsetzung viel Arbeit bei der Implementierung unserer Projekte abgenommen. LINQ hat mit seinen starken Abfragemöglichkeiten, die ebenfalls als Extensions implementiert sind, viele praktische Methoden hinzugefügt. Daher braucht es auch nur wenig Handarbeit, um einige verbleibende Lücken zu schließen.

[ header = Seite 3: Extensions im Einsatz unter System.Data ]

Extensions im Einsatz unter System.Data

Der System.Data Namespace ist seit jeher gekennzeichnet von komplizierten und umfangreich zu implementierenden Methoden. Zwei sehr häufige, aber dennoch umständlich umzusetzende Anwendungsfälle sind hier das sichere Auslesen von Daten aus einem Reader und das Hinzufügen von Parametern zu einem SqlCommand, um SQL-Abfrage und Prozeduraufrufe mit den benötigten Parametern zu versehen.

Felder aus einem offenem Reader auszulesen und dabei leere Felder, DBNull und default-Werte zu behandeln, ist normalerweise eine einfache Aufgabe, aber bei vielen Feldern im Ergebnis der Abfrage wird sie ziemlich umfangreich und erfordert viele Zeilen Code.

Die Extension des Readers behandelt dann diese Aufgaben. Der Aufruf beim Schleifendurchlauf reduziert sich dabei auf eine einzelne Zeile pro Feld (Listing 10).

public static TValue GetSafeValue(this IDataReader reader, string name)
{
  object value;
  try
  {
    value = reader[name];
    if(DBNull.Value.Equals(value))
    {
      return default(TValue);
    }
  }
  catch
  {
    value = default(TValue);
  }
  return (TValue)value;
}

Auch das Hinzufügen von Parametern zu einem Command-Objekt ist keine große Aufgabe, ergibt aber bei einer größeren Anzahl von Parametern erneut eine gehörige Menge Quelltext. Die Extension des Command-Objekts verringert die absolute Zeilenanzahl im Quelltext und erhöht dadurch die Lesbarkeit und Codequalität und somit die Wartbarkeit des Gesamtprojekts (Listing 11).

public static DbParameter AddParameter(this DbCommand cmd, string parameterName, object value)
{
  DbParameter param = cmd.CreateParameter();
  param.ParameterName = parameterName;
  if(value == null)
  {
    param.Value = DBNull.Value;
  }
  else
  {
    param.Value = value;
  }
  cmd.Parameters.Add(param);
  return param;
}

Der Namespace bietet natürlich einen noch viel größeren Spielraum für Verbesserungen und Erweiterungen. Letztlich wollen wir ja unsere Arbeit so weit vereinfachen, dass wir weniger Quelltext schreiben müssen und mehr Features entwickeln können. Gerade die Abstrahierung im Datenbankbereich ist ein Kapitel für sich.

Noch ein paar spezielle Extensions zum Schluss

Neben den allgemein einsetzbaren Extensions gibt es auch viele einzelne Bereiche, die wir erweitern wollen. Zum Beispiel muss man unter ASP.NET manchmal den QueryString, also den Aufruf des Browsers an den Server, auslesen und auf einzelne Parameter zugreifen, um sie zu verarbeiten. Leider bietet das Uri-Objekt nur den QueryString als einzelnen langen String, den es noch zu parsen gilt (Listing 12).

public static Dictionary ParameterList(this Uri instance)
{
  IList list = instance.Parameters();
  Dictionary dict = new Dictionary();
  
  foreach(UriParameter param in list)
  {
    dict.Add(param.Key, param.Value);
  }
  
  return dict;
}

public class UriParameter
{
  public string Key { get; set; }
  public string Value { get; set; }
}

public static IList Parameters(this Uri instance)
{
  string[] _params = instance.Query.Split(
    new string[] { "&" },
    StringSplitOptions.None
  );
  
  List list = new List();
  
  foreach(string par in _params)
  {
    string[] _sep = par.Split(new char[] { '=' });
    string _key = "";
    string _value = "";
    
    if(_sep.Length > 0)
    {
      _key = _sep[0].Replace("?", "").Replace("&", "");
    }
    
    if(_sep.Length > 1)
    {
      _value = _sep[1].Replace("?", "").Replace("&", "");
    }
    
    list.Add(new UriParameter() { Key = _key, Value = _value });
  }
  
  return list;
}

In die andere Richtung geht es natürlich auch, zum Beispiel als Erweiterung des KeyValuePair-Objekts oder anderer Objekte. Hier wird ein Paar aus Schlüssel und Wert im Query-Format des Browsers (&Key=Value) zurückgeliefert. Diese Funktionalität kann auch das Dictionary-Objekt erweitern und somit in beide Richtungen fungieren (Listing 13).

// KeyValuePairExtensions
public static string ToParamString(this KeyValuePair instance)
{
  StringBuilder str = new StringBuilder();
  string _key = (string)((object)instance.Key);
  string _value = (string)((object)instance.Value);
  
  if(!_key.IsNullOrWhiteSpace())
  {
    str.Append("&");
  }
  
  if(!_key.IsNullOrWhiteSpace())
  {
    str.Append(_key);
  }
  
  if(!_value.IsNullOrWhiteSpace())
  {
    str.Append("=");
    str.Append(_value);
  }
  
  return str.ToString();
}

// DictionaryExtensions
public static string ToParamString(this Dictionary instance, bool asAbsolut)
{
  StringBuilder str = new StringBuilder();
  
  if(asAbsolut)
  {
    str.Append("?");
  }
  
  bool isFirst = true;
  
  foreach(KeyValuePair item in instance)
  {
    string _key = (string)((object)item.Key);
    string _value = (string)((object)item.Value);
    
    if(!_key.IsNullOrWhiteSpace())
    {
      if(isFirst && !asAbsolut)
      {
        str.Append("&");
      }
      else if(!isFirst)
      {
        str.Append("&");
      }
      
      str.Append(_key);
    }
    
    if(!_value.IsNullOrWhiteSpace())
    {
      str.Append("=");
      str.Append(_value);
    }
    
    isFirst = false;
  }
  
  return str.ToString();
}

Fazit

Ich hoffe, ich konnte Ihnen ein paar Ideen für Ihren Alltag mitgeben. Die Extensions können die Arbeit wirklich erleichtern und sollten ein fester Bestandteil Ihrer Projekte werden. Es lohnt sich immer, ein kleines Toolsprojekt im Bestand zu haben, es langsam weiter zu verfeinern und in Ihren Anwendungen zu verwenden. Und die Extensions können den Grundstein für ein solches Toolsprojekt legen.

Weitere Informationen

Ich habe alle in diesem Artikel aufgeführten Methoden sowie viele weitere in einem Beispielprojekt bereitgestellt. Der Quelltext steht in meinem GitHub Repository [1] und auf meiner privaten Webseite [2] zum Download bereit.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -