Besser mit Strings programmieren - was mit LINQ möglich wird

Es gibt zwei Welten in der Softwareentwicklung. Objektorientierte Welt beschäftigt sich mit Klassen, Objekten, Ableitungshierarchien, Schnittstellen, Polymorphismus, etc. Relationale Welt will alles in Tabellen, Feldern und Datensätzen sehen. Diese zwei Welten müssen aber in vielen kommerziellen Anwendungen friedlich koexistieren. Die Entwickler sind oft damit beschäftigt die Brücken zwischen den beiden Welten zu bauen. Diese Brücken, dieser Übergang von einer Welt in die andere gestaltet sich nicht immer einfach. Um diesen Übergang bequemer zu machen, hat Microsoft das .NET Framework um eine neue Klassenbibliothek erweitert - System.Linq. LINQ steht für Language Integrated Query. Sinngemäß ins Deutsche übersetzt: in Programmiersprache eingebautes Abfragemechanismus.

Was kann man denn mit LINQ abfragen? Ziemlich alles. Vorausgesetzt man hat einen passenden LINQ-Provider. Microsoft bietet zurzeit folgende LINQ-Provider:

  • LINQ-to-Objects
  • LINQ-to-SQL
  • LINQ-to-DataSet
  • LINQ-to-XML
  • LINQ-to-Entities
  • LINQ-to-DataService

Ich erwähne auch andere interessante LINQ-Provider, die entweder kommerzielle Produkte sind, oder von Open-Source-Gemeinde stammen. Manche Open-Source-Projekte befinden sich noch in Entwicklungsphase.

  • LINQ-to-XSD
  • LINQ-to-Java
  • LINQ-to-PHP
  • LING-to-JavaScript (JSLINQ)
  • LINQ-to-WMI
  • LINQ-to-LDAP
  • LINQ-to-MySq1
  • LINQ-to-SAP
  • LINQ-to-Excel
  • LINQ-to-Google
  • LINQ-to-Freebase

In diesem Artikel möchte ich ein Paar Code-Beispiele vorstellen, die die Benutzung von LINQ für die Arbeit mit Zeichenfolgen illustrieren. Der Artikel ist für Einsteiger in LINQ gedacht. Gewisse Erfahrung in .NET Framework wird dennoch vorausgesetzt.

Wie oft ein bestimmtes Wort in einer Zeichenfolge vorkommt

Im folgenden Code wird untersucht wie oft in dem gegebenen Text die Zeichenfolge "LINQ" vorkommt. Es wird dabei keine Unterscheidung zwischen Groß- und Kleinschreibung gemacht.

StringBuilder sb = new StringBuilder();
sb.Append("LINQ (Abkürzung für Language Integrated Query) ist eine Komponente von Microsofts .NET-Framework");
sb.Append(" zur Abfrage von Datenquellen wie Datenbanken oder XML-Dateien.");
sb.Append(" Besonderheit ist, dass SQL-, XLink- und XQuery-Anfragen direkt in .NET-Programmiersprachen wie C# ");
sb.Append(" oder VB.Net 9.0 als Code statt als String eingebunden werden können.");
sb.Append(" LINQ wurde federführend von Erik Meijer (Informatiker) entwickelt.");
sb.Append(" Der Vorteil von LINQ besteht darin, dass der Code durch den Compiler auf Fehler geprüft");
sb.Append(" und von den anbietenden Bibliotheken optimiert, oder anderweitig manipuliert — zum Beispiel übersetzt — werden kann.");

// Zu durchsuchender Text
string text = sb.ToString();
string[] wortArray = text.Split(' ', ',', '.', '-', '!', '?', ':', ';');

// Gesuchtes Wort
string gesuchtesWort = "linq";

// LINQ-Abfrage
var query = from wort in wortArray
    where wort.ToLowerInvariant() == gesuchtesWort.ToLowerInvariant()
    select wort;

int anzahlVorkommnisse = query.Count();

Kommentare zum Code. Mit einer Instanz der StringBuilder-Klasse wird in dem Beispiel eine durchzusuchende Zeichenfolge erzeugt. Diese Zeichenfolge wird anschließend dem String-Variable text zugewiesen. Die Funktion Split() nimmt die Zeichenfolge auseinander und liefert ein Array der Wörter - wortArray. Das wortArray wird dann mit LINQ nach Vorkommnnissen des Worts linq abgefragt. Bei der LINQ-Abfrage notieren wir eine SQL-ähnliche Syntax mit where, select und in Schlüsselwörtern. Das Ergebnis der Abfrage wird in Variable query gespeichert. Bitte darauf achten, dass diese Variable einen anonymen Typ hat. Anonymer Typ bedeutet, dass wir der Variable beliebige Objekte zuweisen können. Wenn wir den Code debuggen und Laufzeittyp der Variable query untersuchen, dann lernen wir den Typ System.Linq.Enumerable.WhereArrayIterator<string> . In der letzten Zeile ermitteln wir Anzahl der Vorkommnisse vom Wort linq mit Hilfe der Methode Count().

Filtern der Sätze mit bestimmten Wörtern

Im folgenden Code werden aus dem Text nur die Sätze herausgefiltert, die das Wort Framework beinhalten.

// Zu durchsuchender Text
string text = @"LINQ (Abkürzung für Language Integrated Query) ist eine Komponente von Microsofts .NET-Framework zur" +
    @"Abfrage von Datenquellen wie Datenbanken oder XML-Dateien. Besonderheit ist, dass SQL-, XLink- und " +
    @"XQuery-Anfragen direkt in .NET-Programmiersprachen wie C# 3.0 oder VB.Net 9.0 als Code statt als String" +
    @"eingebunden werden können. LINQ wurde federführend von Erik Meijer (Informatiker) entwickelt." +
    @"Das LINQ-Framework enthält zudem das Tool SQLMetal, welches die automatische Codegenerierung von" +
    @"Wrapper-Klassen für Microsoft SQL Server-Datenbanken in C# mit DLINQ ermöglicht," +
    @"was Softwareentwicklern einen zusätzlichen Komfort bei der Applikationsentwicklung bietet.";

// Text in Sätze aufeilen
string[] saetze = text.Split('.', '!', '?');

// Array mit gesuchten Wörtern
string[] gesuchteWoerter = {"Framework"};

// LINQ-Abfrage
var query = from satz in saetze
    let w = satz.Split(new char[] { '.', ',', ':', ';', '?', '!', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries)
    where w.Distinct().Intersect(gesuchteWoerter).Count() == gesuchteWoerter.Count()
    select satz;

// Ergebnis-Array mit Sätzen, die das Wort Framework inkludieren
string[] resultArray = new string[query.Count()];
int i = 0;
foreach (string s in query)
{
    resultArray[i] = s;
    i++;
}

Kommentare zum Code. In String-Variable text haben wir zunächst alle Sätze, die wir danach untersuchen wollen, ob und welche das Wort Framework in sich haben. Analog dem ersten Beispiel wird der Text in Array saetze mit einzelnen Sätzen aufgeteilt. Array gesuchteWoerter hat nur ein Element - das Wort Framewort. Das Array gesuchteWoerter repräsentiert die Menge der Wörter, die wir im Text suchen. Anschließend wird eine Abfrage mit LINQ aufgebaut. Hier merken wir das Schlüsselwort let, das es im vorigen Beispiel nicht gab. Mit dem Schlüsselwort let kann man in LINQ-Abfragen eine Art Unterfunktionen implementieren. In diesem Fall bedienen wir uns der Unterfunktion Split und teilen einen laufenden Satz in einzelne Wörter auf. Am Ende werden die gefilterten Sätze aus dem Iterator in Ergebnisarray gespeichert.

Zwei Texte vergleichen

In diesem Beispiel wird gezeigt wie man zwei Texte mit Hilfe von LINQ-Abfrage vergleichen kann. Wie bedienen uns dem Text, den wir in vorigem Beispiel benutzt haben. Davon erstellen wir eine zweite Kopie. In der Kopie bauen wir einen absichtlichen Fehler ein: wir ersetzen alle LINQ Zeichenfolgen mit LING (Fehlerhafte Schreibweise) Zeichenfolgen. Unser Beispielcode wird diese Differenzen herausfinden.

// Text
string text1 = @"LINQ (Abkürzung für Language Integrated Query) ist eine Komponente von Microsofts .NET-Framework zur" +
    @"Abfrage von Datenquellen wie Datenbanken oder XML-Dateien. Besonderheit ist, dass SQL-, XLink- und " +
    @"XQuery-Anfragen direkt in .NET-Programmiersprachen wie C# 3.0 oder VB.Net 9.0 als Code statt als String" +
    @"eingebunden werden können. LINQ wurde federführend von Erik Meijer (Informatiker) entwickelt." +
    @"Das LINQ-Framework enthält zudem das Tool SQLMetal, welches die automatische Codegenerierung von" +
    @"Wrapper-Klassen für Microsoft SQL Server-Datenbanken in C# mit DLINQ ermöglicht," +
    @"was Softwareentwicklern einen zusätzlichen Komfort bei der Applikationsentwicklung bietet.";

// Textkopie mit eingebautem Fehler
string text2 = @"LING (Abkürzung für Language Integrated Query) ist eine Komponente von Microsofts .NET-Framework zur" +
    @"Abfrage von Datenquellen wie Datenbanken oder XML-Dateien. Besonderheit ist, dass SQL-, XLink- und " +
    @"XQuery-Anfragen direkt in .NET-Programmiersprachen wie C# 3.0 oder VB.Net 9.0 als Code statt als String" +
    @"eingebunden werden können. LING wurde federführend von Erik Meijer (Informatiker) entwickelt." +
    @"Das LING-Framework enthält zudem das Tool SQLMetal, welches die automatische Codegenerierung von" +
    @"Wrapper-Klassen für Microsoft SQL Server-Datenbanken in C# mit DLING ermöglicht," +
    @"was Softwareentwicklern einen zusätzlichen Komfort bei der Applikationsentwicklung bietet.";

// Text in Sätze aufeilen
string[] saetze1 = text1.Split('.', '!', '?');
string[] saetze2 = text2.Split('.', '!', '?');

// Differenzen eritteln
IEnumerablestring> query = saetze1.Except(saetze2);

// Ergebis-Array mit Sätzen, die das Wort Framework inkludieren
string[] resultArray = new string[query.Count()];
int i = 0;
foreach (string s in query)
{
    resultArray[i] = s;
    i++;
}

Kommentare zum Code. Die zwei Texte werden zunächst in zwei Variablen definiert. Anschließend werden zwei Arrays - saetze1, saetze2 - mit Sätzen aus den Texten generiert. Methode Except(), die in jedem Array vorhanden ist, erlaubt uns die Sätze, die in  zwei Texten unterschiedlich sind, in Variable query zu speichern. Die Variable query hat den Typ IEnumerable<string>. Am Ende bereiten wir Ergebnisarray in dem wir durch die Elemente von query in foreach-Schleife iterieren.