Programmieren in C#: Einführung


Kapitel 8: LINQ


Inhaltsverzeichnis

Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.


8.1 Allgemeines

Datenbestände abfragen und transformieren

LINQ ist eine Abkürzung für Language-Integrated Query. Damit ist eine Erweiterung der Programmiersprache C# gemeint, die seit der Version 3.0 existiert.

LINQ ermöglicht es, Datenbestände mit Hilfe von Abfragen zu verwalten, die in C#-Quellcode eingebettet werden. Diese Abfragen werden dabei in einer Syntax formuliert, die SQL ähnelt. Das ist deswegen wichtig, weil SQL eine standardisierte Sprache zum Abfragen relationaler Datenbanken ist, mit der viele Entwickler vertraut sind. Während SQL aber nur im Zusammenhang mit relationalen Datenbanken verwendet werden kann, können LINQ-Abfragen auf sehr unterschiedliche Datenbestände angewandt werden. Somit bietet C# mit LINQ ein Instrument an, um beliebige Daten auf eine Art und Weise zu verarbeiten, wie sie vielen Entwicklern mit SQL vertraut ist.

Neben C# wird LINQ auch von anderen Programmiersprachen wie Visual Basic unterstützt. Denn LINQ ist genaugenommen kein Merkmal von C# oder Visual Basic, auch wenn diese Programmiersprachen das Formulieren von LINQ-Abfragen durch eine spezielle Syntax vereinfachen. So wie die generische Programmierung explizit vom .NET-Framework unterstützt wird, ist auch LINQ Bestandteil des .NET-Frameworks. Es ist daher möglich, mit LINQ in jeder beliebigen .NET-Programmiersprache zu arbeiten. Einzige Voraussetzung ist, das .NET-Framework ab der Version 3.5 einzusetzen, da erst ab dieser Version LINQ unterstützt wird.


8.2 Abfragesyntax

Vereinfachte Sprache zum Verarbeiten von Datensätzen

LINQ ist kein alleiniges Sprachmerkmal von C#, sondern steht durch das .NET-Framework allen Programmiersprachen zur Verfügung. Es handelt sich somit um eine vom .NET-Framework bereitgestellte Technologie, die in Form von Klassen und Interfaces implementiert ist. Diese befinden sich vorrangig im Namensraum System.Linq. Die wichtigsten Klassen sind dabei Enumerable und Queryable, die zahlreiche Methoden anbieten, um Datenbestände zu verarbeiten.

Während es möglich ist, die Methoden dieser Klassen direkt zu verwenden, unterstützt C# eine spezielle Abfragesyntax, mit der SQL-ähnliche Abfragen in C#-Code eingebettet werden können. Diese Abfragesyntax ist ein Sprachmerkmal der Programmiersprache C# und soll es Entwicklern leichter machen, Abfragen einzusetzen. Sehen Sie sich dazu im Folgenden die Eigenschaft FavoritesOrderedByName an, die dem Favoriten-Manager hinzugefügt wurde, um Favoriten dem Namen nach sortiert zu erhalten.

using System; 
using System.Linq; 
using System.Collections.Generic; 

public class FavoriteManager 
{ 
  List<Favorite> favs = new List<Favorite>(); 

  public Favorite[] FavoritesOrderedByName 
  { 
    get 
    { 
      IEnumerable<Favorite> favQuery = from fav in favs 
                                       orderby fav.Name ascending 
                                       select fav; 
      return favQuery.ToArray(); 
    } 
  } 
} 

Innerhalb des get-Accessors der Eigenschaft FavoritesOrderedByName wird eine LINQ-Abfrage verwendet. Sie erkennen die Abfrage daran, dass sie ganz anders aussieht als der Code, den Sie bisher in diesem Buch kennengelernt haben. Anstatt auf Objekte zuzugreifen und Eigenschaften und Methoden zu verwenden, werden bei einer LINQ-Abfrage verschiedene Wörter scheinbar wahllos hintereinander gestellt.

Die obige LINQ-Abfrage greift auf die Liste favs zu, um ihre Einträge dem Namen nach zu sortieren. In dieser LINQ-Abfrage wird demnach ein Objekt vom Typ List als Datenquelle verwendet. Das ist deswegen möglich, weil List das Interface IEnumerable aus dem Namensraum System.Collections implementiert.

LINQ-Abfragen können auf beliebige Datenquellen zugreifen, wenn diese das Interface IEnumerable implementieren. Dazu zählt nicht nur die Klasse List. So unterstützt LINQ als Datenquellen auch relationale Datenbanken oder XML-Dateien. Der Vorteil ist, dass Sie mit LINQ-Abfragen Datensätze immer auf die gleiche Art und Weise verarbeiten können - unabhängig davon, wo und wie die Datensätze gespeichert sind. Einmal erlernt können Sie LINQ-Abfragen beim Zugriff auf beliebige Datenbestände verwenden.

Wenn Sie einen Datenbestand verarbeiten möchten - im obigen Beispielcode ist dies die Liste, verankert in der Referenzvariablen favs - müssen Sie eine LINQ-Abfrage erstellen. Bei dieser Abfrage handelt es sich um ein Objekt, das wie üblich in einer Referenzvariable gespeichert wird. Im obigen Beispielcode ist dies favQuery.

Der Datentyp von favQuery ist IEnumerable, also ein Interface. Auf welcher Klasse das Objekt basiert, das die LINQ-Abfrage darstellt, ist zweitrangig. Wichtig ist, dass dieses Objekt das Interface IEnumerable implementiert. Da die Methoden, die LINQ anbietet, über dieses Interface zur Verfügung gestellt werden, wird im Zusammenhang mit LINQ-Abfragen IEnumerable verwendet.

Selbstverständlich können Sie auch mit dem Schlüsselwort var den Datentyp automatisch vom C#-Compiler ermitteln lassen. In diesem Fall greift der Compiler auch auf IEnumerable zu. Welche Klasse auch immer verwendet wird, um dieses Interface zu implementieren, sie bleibt unsichtbar.

Wie sieht die Abfragesyntax von C# im Detail aus? So merkwürdig die Abfragesyntax auf den ersten Blick sein mag, wird sie vom Compiler derart übersetzt als handelt es sich um herkömmliche Methodenaufrufe. So kann die LINQ-Abfrage, die im obigen Beispielcode verwendet wird, auch genausogut wie folgt geschrieben werden:

IEnumerable<Favorite> favQuery = favs.OrderBy(fav => fav.Name); 

Der C#-Compiler generiert den gleichen Binärcode unabhängig davon, ob Sie die spezielle Abfragesyntax von C# verwenden oder wie gewohnt Methoden aufrufen. In der Praxis wird die Abfragesyntax vorgezogen, weil es sich um ein Instrument handelt, das speziell zur Entwicklung von Abfragen geschaffen wurde: Abfragen sollen einfacher zu erstellen und zu verstehen sein.

Die Abfragesyntax von C# kennt zahlreiche Schlüsselwörter wie from, in, orderby, ascending und select. Grundsätzlich beginnt eine Abfrage immer damit, dass mit from eine Datenquelle ausgewählt wird. Diese wird hinter dem Schlüsselwort in angegeben. Um auf Datensätze aus dieser Datenquelle in der LINQ-Abfrage verweisen zu können, muss außerdem eine Variable definiert werden. Diese steht vor dem Schlüsselwort in.

Nachdem die Datenquelle ausgewählt wurde, kann der Datenbestand bearbeitet werden. So wird zum Beispiel über orderby angegeben, dass die Datensätze über den Namen aufsteigend sortiert werden sollen. Dies geschieht, indem über die Variable fav auf die Eigenschaft Name zugegriffen wird. Dies funktioniert natürlich nur deswegen, weil fav Objekte vom Typ Favorite repräsentiert und diese Klasse eine Eigenschaft Name anbietet.

LINQ kennt neben ascending auch ein Schlüsselwort descending, um Daten absteigend zu sortieren.

Im letzten Schritt wird mit select auf die Daten zugegriffen, die als Ergebnis zurückgegeben werden sollen. Dabei werden diese Daten selbstverständlich in der Reihenfolge zurückgegeben, wie sie im vorherigen Schritt mit orderby festgelegt wurde.

Viele Schlüsselwörter wie orderby entsprechen Methoden, die vom Interface IEnumerable angeboten werden. Diese Methoden sind merkwürdigerweise jedoch nicht im Interface IEnumerable definiert. Wenn Sie die Dokumentation von IEnumerable öffnen, sehen Sie, dass zwischen herkömmlichen Methoden und sogenannten Erweiterungsmethoden unterschieden wird.

Erweiterungsmethoden sind Methoden, die von einer Klasse oder einem Interface angeboten werden, der Klasse oder dem Interface jedoch durch eine andere Klasse hinzugefügt worden sind. Derartige Erweiterungen quer über Klassen- und Interfaceschnittstellen werden von LINQ genutzt, um im Interface IEnumerable zusätzliche Methoden bereitzustellen, die zur Verarbeitung von Datenbeständen nützlich sein können. Da solche klassen- und interfaceübergreifenden Erweiterungen nicht gerade zu einer besseren Code-Struktur beitragen, sollten sie nur in Ausnahmefällen verwendet werden. In diesem Buch werden sie daher auch nicht näher vorgestellt.

Wichtig ist, dass die Erweiterungsmethoden im Interface IEnumerable nur dann zur Verfügung stehen, wenn mit using der Namensraum System.Linq bekannt gemacht wird. Nur dann erweitert LINQ das Interface zum Beispiel um eine Methode OrderBy(). Welche Schlüsselwörter welchen Methoden entsprechen können Sie übrigens der Dokumentation zu den sogenannten Standardabfrageoperatoren entnehmen.

Wenn Sie nicht die Abfragesyntax von C# verwenden möchten, können Sie die Methode OrderBy() für favs aufrufen. OrderBy() erwartet als Parameter eine Funktion, die verwendet wird, um die Reihenfolge der Favoriten in der Liste favs zu bestimmen. Häufig werden an dieser Stelle Lambda-Funktionen verwendet. Wenn die Parameterliste einer Lambda-Funktion nur einen einzigen Parameter enthält und die Funktionsdefinition lediglich aus einer return-Anweisung besteht, können die runden Klammern um die Parameterliste, die geschweiften Klammern der Funktionsdefinition und das return-Schlüsselwort weggelassen werden. Die obige Lambda-Funktion, die als Parameter an OrderBy() übergeben wird, besitzt demnach einen Parameter namens fav und greift auf die Eigenschaft Name zu, um den entsprechenden Wert zurückzugeben. So können Sie die Lambda-Funktion auch wie folgt angeben:

IEnumerable<Favorite> favQuery = favs.OrderBy((fav) => { return fav.Name; }); 

Der Parameter, der an die Lambda-Funktion übergeben wird, hat den gleichen Datentyp wie die Objekte, die im Datenbestand verwaltet werden. Da OrderBy() für eine Liste aufgerufen wird, in der Objekte vom Typ Favorite gespeichert sind, bezieht sich fav jeweils auf ein Objekt aus dieser Liste. Da in der Liste jedoch beliebig viele Favoriten gespeichert sein können, in der Lambda-Funktion aber jeweils auf genau einen Favoriten zugegriffen und der entsprechende Name zurückgegeben wird, stellt sich die Frage, wie die LINQ-Abfrage im Detail ausgeführt wird.

Eine LINQ-Abfrage wird verzögert ausgeführt. Egal, ob Sie die Abfragesyntax von C# verwenden oder Methoden direkt aufrufen - Sie erstellen eine Abfrage und speichern sie zur späteren Verwendung. Beim Erstellen einer LINQ-Abfrage passiert also erstmal gar nichts mit dem entsprechenden Datenbestand. Es wird lediglich ein Objekt erstellt, das in einer Variable vom Typ IEnumerable verankert wird. Mit diesem Objekt muss dann in einem zweiten Schritt gearbeitet werden, um eine LINQ-Abfrage tatsächlich einzusetzen und auszuführen.

Es gibt grundsätzlich zwei Möglichkeiten, eine LINQ-Abfrage auszuführen. Die eine Möglichkeit besteht darin, dass Sie so wie im obigen Beispielcode eine entsprechende Methode wie ToArray() aufrufen, die von IEnumerable zur Verfügung gestellt wird und in diesem Fall das entsprechend sortierte Array vom Typ Favorite zurückgibt. Die andere Möglichkeit besteht in einer Iteration über eine foreach-Schleife:

var orderedFavs = new List<Favorite>(); 
foreach (var fav in favQuery) 
{ 
  orderedFavs.Add(fav); 
} 

Die foreach-Schleife funktioniert mit jeder Auflistung, die das Interface IEnumerable implementiert. Da dies genau der Datentyp ist, der für LINQ-Abfragen verwendet wird, kann mit foreach über die Ergebnisse der Abfrage iteriert werden. Dabei wird die Abfrage erst dann ausgeführt, wenn mit der foreach-Schleife begonnen wird, auf Ergebnisse zuzugreifen.


8.3 Filter

Datensätze verringern

Jede LINQ-Abfrage beginnt mit dem Schlüsselwort from, um eine Datenquelle auszuwählen. Außerdem wird eine Variable definiert, über die auf Datensätze verwiesen werden kann, um diese in irgendeiner Weise zu bearbeiten. So ist es zum Beispiel möglich, mit LINQ-Abfragen Filter zu erstellen. Im LINQ-Programmierhandbuch finden Sie eine Dokumentation zu Filtern.

using System; 
using System.Linq; 
using System.Collections.Generic; 

public class FavoriteManager 
{ 
  List<Favorite> favs = new List<Favorite>(); 

  public Favorite[] FavoritesFilteredByHighscore 
  { 
    get 
    { 
      IEnumerable<Favorite> favQuery = from fav in favs 
                                       where fav.URL.Host == "www.highscore.de" 
                                       select fav; 
      return favQuery.ToArray(); 
    } 
  } 
} 

Im obigen Beispielcode wurde der Favoriten-Manager um eine Eigenschaft FavoritesFilteredByHighscore erweitert, die lediglich Favoriten für Webseiten zurückgibt, die auf dem Server www.highscore.de liegen. Dazu wurde auf das Schlüsselwort where zugegriffen, mit dem Bedingungen überprüft werden können. So werden im obigen Beispielcode von der Abfrage nur Objekte vom Typ Favorite zurückgegeben, deren URL-Eigenschaft einen Wert www.highscore.de in der Host-Eigenschaft besitzt.

using System; 
using System.Linq; 
using System.Collections.Generic; 

public class FavoriteManager 
{ 
  List<Favorite> favs = new List<Favorite>(); 

  public Favorite[] FavoritesFilteredByHighscore 
  { 
    get 
    { 
      IEnumerable<Favorite> favQuery = from fav in favs 
                                       where fav.URL.Host == "www.highscore.de" && fav.URL.AbsolutePath.StartsWith("/csharp") 
                                       select fav; 
      return favQuery.ToArray(); 
    } 
  } 
} 

Es ist möglich, mehrere Bedingungen mit dem &&-Operator zu verknüpfen. So werden im obigen Beispielcode nur Favoriten zurückgegeben, die auf dem Server www.highscore.de liegen und über einen Pfad erreichbar sind, der mit /csharp beginnt.


8.4 Sortierung

Daten ordnen

Sie haben in diesem Kapitel bereits gesehen, wie Sie mit orderby eine Sortierung vornehmen können. Im folgenden Beispiel soll daher die Sortierung mit einem Filter verknüpft werden. Eine Dokumentation zur Sortierung finden Sie ebenfalls im LINQ-Programmierhandbuch.

using System; 
using System.Linq; 
using System.Collections.Generic; 

public class FavoriteManager 
{ 
  List<Favorite> favs = new List<Favorite>(); 

  public Favorite[] FavoritesOrderedAndFilteredByHighscore 
  { 
    get 
    { 
      IEnumerable<Favorite> favQuery = from fav in favs 
                                       where fav.URL.Host == "www.highscore.de" 
                                       orderby fav.URL.Port, fav.URL.AbsolutePath 
                                       select fav; 
      return favQuery.ToArray(); 
    } 
  } 
} 

Die Eigenschaft FavoritesOrderedAndFilteredByHighscore gibt immer noch Favoriten zurück, die auf Webseiten auf dem Server www.highscore.de zeigen. Diesmal sind diese Favoriten jedoch sortiert - und zwar sowohl nach dem Port als auch nach dem Pfad. Die Sortierung erfolgt dabei derart, dass alle Favoriten aufsteigend nach dem Port sortiert werden. Die Favoriten mit dem gleichen Port werden dann nochmal aufsteigend nach dem Pfad sortiert.

Beachten Sie, dass orderby standardmäßig eine aufsteigende Sortierung vornimmt. Das Schlüsselwort ascending ist optional. Möchten Sie eine absteigende Sortierung vornehmen, müssen Sie jedoch descending angeben.


8.5 Gruppierung

Daten gruppieren

Mit den Schlüsselwörtern group, by und into können Daten zu Gruppen zusammengestellt werden. Während hinter group auf die Variable zugegriffen wird, die vormals hinter from definiert wurde, wird hinter by der Schlüssel angegeben, der zur Gruppenbildung herangezogen werden soll. Hinter into wiederum wird eine neue Variable definiert, die verwendet werden kann, um Gruppen zu bearbeiten. In der Dokumentation zur Gruppierung im LINQ-Programmierhandbuch finden Sie wie üblich weitergehende Informationen.

using System; 
using System.Linq; 
using System.Collections.Generic; 

public class FavoriteManager 
{ 
  List<Favorite> favs = new List<Favorite>(); 

  public Favorite[] FavoritesGrouped 
  { 
    get 
    { 
      IEnumerable<IGrouping<string, Favorite>> favQuery = from fav in favs 
                                                          group fav by fav.URL.Host into favHosts 
                                                          select favHosts; 
      List<Favorite> groupedFavs = new List<Favorite>(); 
      foreach (var group in favQuery) 
      { 
        foreach (var fav in group) 
        { 
          groupedFavs.Add(fav); 
        } 
      } 
      return groupedFavs.ToArray(); 
    } 
  } 
} 

Im obigen Beispielcode gibt die LINQ-Abfrage Gruppen von Favoriten zurück, die jeweils auf Webseiten auf dem gleichen Server verweisen. Dies zeigt sich auch im geänderten Datentyp der Variable favQuery, der nun IEnumerable<IGrouping<string, Favorite>> lautet. Beim generischen Interface IGrouping, das im Namensraum System.Linq definiert ist, handelt es sich um eine Beschreibung von Gruppen. Die Datentypen, die als Parameter in spitzen Klammern übergeben werden müssen, kennzeichnen den Schlüssel und die Elemente der Gruppe.

Wenn Sie Daten in LINQ-Abfragen gruppieren und in einer foreach-Schleife verarbeiten möchten, verwenden Sie üblicherweise zwei verschachtelte Schleifen. Während die äußere Schleife Objekte zurückgibt, die IGrouping implementieren, ermöglicht Ihnen die innere Schleife, Zugriff auf die Objekte in den Gruppen zu nehmen.

Beachten Sie, dass es möglich ist, Elemente in Gruppen in der LINQ-Abfrage zu filtern oder sortieren. So können die Schlüsselwörter where und orderby hinter group verwendet werden. Dabei muss dann auf die Variable zugegriffen werden, die hinter into angegeben ist und eine Gruppe repräsentiert. Weil diese Variable nur eine einzige Eigenschaft Key anbietet, die den Schlüssel repräsentiert, nach dem die Gruppenbildung erfolgt, sind die Möglichkeiten für eine weitergehende Verarbeitung von Gruppen jedoch begrenzt.


8.6 Projektion

Datenbestände umwandeln

Bei einer Projektion werden Datensätze nicht nur gefiltert oder sortiert. Es wird nur ein Teil jedes gefundenen Datensatzes zurückgegeben. Wie gewohnt enthält das LINQ-Programmierhandbuch eine entsprechende Dokumentation zur Projektion.

using System; 
using System.Linq; 
using System.Collections.Generic; 

public class FavoriteManager 
{ 
  List<Favorite> favs = new List<Favorite>(); 

  public string[] FavoriteNamesOrdered 
  { 
    get 
    { 
      IEnumerable<string> favQuery = from fav in favs 
                                     orderby fav.Name 
                                     select fav.Name; 
      return favQuery.ToArray(); 
    } 
  } 
} 

Im obigen Beispielcode gibt die Eigenschaft FavoriteNamesOrdered ein string-Array zurück, das die Namen aller Favoriten aufsteigend sortiert enthält. Während die Datenquelle, auf die die LINQ-Abfrage zugreift, Objekte vom Typ Favorite enthält, werden von der LINQ-Abfrage Strings zurückgegeben - eine typische Projektion.


8.7 Aggregatsoperationen

Ergebnisse zusammenfassen

Aggregationsoperationen werden auf Ergebnisse von LINQ-Abfragen angewandt. Es handelt sich hierbei um herkömmliche Methodenaufrufe, um anstatt mehrerer Datensätze eine Zahl als Ergebnis zu erhalten - zum Beispiel die Anzahl aller gefundenen Datensätze. Die Dokumentation zu den Aggregationsoperationen im LINQ-Programmierhandbuch enthält weitere Details.

using System; 
using System.Linq; 
using System.Collections.Generic; 

public class FavoriteManager 
{ 
  List<Favorite> favs = new List<Favorite>(); 

  public int Length 
  { 
    get 
    { 
      return (from fav in favs 
              select fav).Count(); 
    } 
  } 
} 

Im obigen Beispielcode ist im Favoriten-Manager eine Eigenschaft Length definiert, die die Anzahl aller Favoriten zurückgeben soll. Dabei wird eine Aggregationsoperation Count() verwendet, die für das Ergebnis der LINQ-Abfrage aufgerufen wird.

Beachten Sie, dass der Aufruf von Aggregationsoperationen dazu führt, dass die LINQ-Abfrage tatsächlich ausgeführt wird. Denn nur dann kann die Aggregationsoperation auch ein Ergebnis zurückliefern.


8.8 Zusammenfassung

Datenverwaltung leicht gemacht

Die in diesem Kapitel vorgestellten LINQ-Abfragen greifen alle auf eine Liste als Datenquelle zu. Der Zugriff auf Objekte als Datenquelle wird als LINQ-to-Objects bezeichnet. Wenn ein Objekt die Schnittstelle IEnumerable anbietet, kann es als Datenquelle in LINQ-Abfragen verwendet werden - so wie eine Liste.

Neben LINQ-to-Objects kennt das .NET-Framework auch LINQ-to-XML und LINQ-to-ADO.NET, um XML-Dateien und relationale Datenbanken als Datenquellen zu verwenden. Somit stellt LINQ ein Allzweckwerkzeug dar, um Datenbestände zu verarbeiten. Selbst dann, wenn Sie lediglich mit einfachen Objekten wie beispielsweise Arrays arbeiten, kann LINQ Sinn machen, da LINQ-Abfragen typischerweise kürzer und daher einfacher zu entwickeln und zu lesen sind als Code, der auf Konstrollstrukturen wie Schleifen zugreift.

Während Sie in diesem Kapitel die Abfragesyntax in C# kennengelernt haben, gibt es zahlreiche Methoden, die nicht über Schlüsselwörter in C# verwendet werden können. In der Übersicht über die Standardabfrageoperatoren werden Methoden und Schlüsselwörter gegenüber gestellt. So gibt es beispielsweise Methoden zur Partionierung von Daten. LINQ erweitert das Interface IEnumerable um eine Methode Take(), mit der eine bestimmte Anzahl an Datensätzen aus dem Ergebnis einer Abfrage übernommen werden können. Da es kein Schlüsselwort in C# gibt, um Daten mit Hilfe der speziellen Abfragesyntax zu partitionieren, müssen Sie eine Methode wie Take() direkt aufrufen. Insofern kann ein Blick in die Dokumentation hilfreich sein, da die Schlüsselwörter der speziellen Abfragesyntax in C# nicht alle LINQ-Funktionen zur Verfügung stellen.


8.9 Aufgaben

Übung macht den Meister

Sie können die Lösungen zu allen Aufgaben in diesem Buch als ZIP-Datei erwerben.

  1. Ändern Sie Ihre Lösung zur Aufgabe 4 aus Abschnitt 6.6, „Aufgaben“ derart, dass in der Menüleiste ein Menü Favoriten erscheint, dass die drei Menüeinträge Hinzufügen, Aufsteigend sortieren und Absteigend sortieren enthält. Während bei einem Klick auf Hinzufügen wie zuvor die aktuell sichtbare Webseite im Favoriten-Manager gespeichert werden soll, sollen bei einem Klick auf die anderen beiden Menüeinträge die Favoriten dem Namen noch sortiert werden.

    Fügen Sie zum Beispiel zwei Methoden SortAscending() und SortDescending() der Klasse FavoriteManager hinzu, die Sie in den Event-Handlern für die Menüeinträge zur Sortierung der Favoriten aufrufen. Erstellen Sie geeignete LINQ-Abfragen, die die Sortierung der Favoriten vornehmen.

  2. Fügen Sie dem Menü Favoriten, das Sie in Ihrer Lösung zur Aufgabe 1 entwickelt haben, einen Menüeintrag Favoriten auf anderen Servern ausblenden hinzu. Mit diesem sollen alle Favoriten ausgeblendet werden, die auf andere Server zeigen als der Server, von dem die aktuell sichtbare Webseite stammt. Wenn im Favoriten-Manager zum Beispiel Favoriten für Webseiten auf www.highscore.de und www.microsoft.de gespeichert sind und im Moment eine Webseite von www.highscore.de sichtbar ist, sollen ausschließlich die zu diesem Server gehörenden Favoriten angezeigt werden. Über einen Menüeintrag Alle Favoriten einblenden sollen alle Favoriten wieder eingeblendet werden können.

    Sie können so vorgehen wie in Ihrer Lösung zur Aufgabe 1. Erweitern Sie beispielsweise den Favoriten-Manager um zwei Methoden, über die die Favoriten ein- und ausgeblendet werden können.

  3. In der Statusleiste soll jeweils angezeigt werden, wie viele Gruppen an Favoriten im Moment gespeichert sind. Dabei sollen Favoriten, die auf Webseiten auf dem gleichen Server zeigen, jeweils eine Gruppe bilden.

    Um die Anzeige in der Statusleiste zu aktualisieren, wenn sich die Anzahl der Gruppen ändert, können Sie zum Beispiel eine neue Event-Eigenschaft GroupsChanged dem Favoriten-Manager hinzufügen. Die Windows Forms-Anwendung kann dann eine Methode an diese Event-Eigenschaft binden und kann auf diese Weise informiert werden, wenn sich die Anzahl der Gruppen ändert.