ein Projekt von goloroden.de

Linq

Was ist Linq?

Während es in früheren Versionen von C# unter Umständen sehr aufwändig war, einzelne Elemente innerhalb einer Aufzählung zu suchen oder diese zu sortieren, stellt C# dafür seit der Version 3.0 eine eigene Abfragesprache zur Verfügung, die als Language Integrated Query, abgekürzt Linq, bezeichnet wird.

Um beispielsweise aus einem Array, das Elemente des Typs string enthält, alle diejenigen zu ermitteln, deren Wert mit einem bestimmten Buchstaben beginnt und diese alphabetisch sortiert auszugeben, wurden zumindest eine Schleife und zahlreiche if-Anweisungen benötigt. Seit der Version 3.0 von C# gibt es dafür eine eigene Abfragesprache, deren Syntax sich an der Datenbanksprache SQL orientiert, die aber über vollständige Unterstützung in C# und Visual Studio verfügt. Durch die Integration in C# wird Linq ebenso wie der übrige Code durch den Compiler in MSIL übersetzt, so dass auf Linq basierende Abfragen auf Fehler überprüft werden können. Im Gegensatz zu klassischen Abfragen, die beispielsweise als Zeichenketten innerhalb von C# vorliegen, können Fehler auf diese Art bereits vor der Ausführung erkannt werden.

Außerdem werden in Linq geschriebene Abfragen ebenfalls von der Common Language Runtime ausgeführt und nutzen daher wie C# ebenfalls die Vorteile von verwalteter Ausführung.

Abfrageoperatoren

Die einfachste Abfrage, die in Linq geschrieben werden kann, ermittelt alle Elemente aus einer Aufzählung, gibt also die Aufzählung selbst zurück, ohne diese zu durchsuchen oder zu sortieren. Um die Fähigkeiten von Linq nutzen zu können, muss der Namensraum System.Linq eingebunden werden, der sich in der Assembly System.Core befindet.

Eine Abfrage wird in Linq mit Hilfe des Schlüsselwortes from eingeleitet, dem ein Bezeichner für ein einzelnes Element folgt. Die Wahl dieses Bezeichners ist beliebig und vergleichbar mit der Wahl des Bezeichners innerhalb einer foreach-Schleife.

Im Anschluss wird mit Hilfe des Schlüsselwortes in angegeben, aus welcher Aufzählung die Elemente stammen, mit dem Schlüsselwort select wird schließlich das jeweilige Element als relevant für die Ergebnismenge ausgewählt.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;
using System.Linq;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Define an array of colors.
            string[] colors =
                new string[] { "Red", "Green", "Blue" };

            // Get all colors.
            var result =
                from c in colors
                select c;

            // Print all colors to the console.
            foreach (var color in result)
            {
                Console.WriteLine(color);
            }
        }
    }
}
Obwohl die Ergebnismenge nur aus Elementen des Typs string besteht, ist es in der Praxis üblich, den Typ einer in Linq geschriebenen Abfrage mit Hilfe von var zu definieren.

Ebenso könnte die Schleifenvariable der foreach-Schleife als string definiert werden, da jedes einzelne Element diesem Typ entspricht, aber auch dies ist in der Praxis unüblich.

Um die Ergebnismenge zu sortieren, kann das Schlüsselwort orderby verwendet werden, wobei nach diesem ein Ausdruck angegeben werden muss, welcher die Sortierreihenfolge definiert. Es ist also nicht nur möglich, nach dem Element an sich zu sortieren, sondern es kann beispielsweise auch eine Eigenschaft oder ein Delegat angegeben werden, der die Sortierung vornimmt.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Linq;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Define an array of colors.
            string[] colors =
                new string[] { "Red", "Green", "Blue" };

            // Get all colors in alphabetical order.
            var result =
                from c in colors
                orderby c
                select c;

            // Print all colors to the console.
            foreach (var color in result)
            {
                Console.WriteLine(color);
            }
        }
    }
}
Alternativ zu der aufsteigenden Sortierung können die Elemente der Ergebnismenge durch die Angabe des zusätzlichen Schlüsselwortes descending auch absteigend sortiert werden.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Linq;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Define an array of colors.
            string[] colors =
                new string[] { "Red", "Green", "Blue" };

            // Get all colors in reversed alphabetical order.
            var result =
                from c in colors
                orderby c descending
                select c;

            // Print all colors to the console.
            foreach (var color in result)
            {
                Console.WriteLine(color);
            }
        }
    }
}
Zusätzlich kann die Ergebnismenge mit dem Schlüsselwort where durchsucht werden, so dass nur einige Elemente in der Ergebnismenge enthalten sind. Im folgenden Beispiel werden beispielsweise nur die Elemente in die Ergebnismenge aufgenommen, deren Anfangsbuchstabe ein R ist.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;
using System.Linq;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Define an array of colors.
            string[] colors =
                new string[] { "Red", "Green", "Blue" };

            // Get all colors whose name starts with an R
            // in reversed alphabetical order.
            var result =
                from c in colors
                where c.StartsWith("R")
                orderby c descending
                select c;

            // Print all colors to the console.
            foreach (var color in result)
            {
                Console.WriteLine(color);
            }
        }
    }
}
Falls der Typ der Elemente der Ergebnismenge kein einfacher Typ, sondern ein komplexer Typ wie beispielsweise ein Objekt ist, kann es gewünscht sein, nicht das gesamte Element in die Ergebnismenge aufzunehmen, sondern nur eine oder mehrere Eigenschaften.

Sofern nur eine einzelne Eigenschaft als Element in die Ergebnismenge aufgenommen werden soll, genügt es, diese an Stelle des eigentlichen Objekts bei select anzugeben. Sollen statt dessen mehrere Eigenschaften verwendet werden, können diese mit Hilfe von Objektinitialisierern in ein neues Objekt von einem anonymen Typ zusammengeführt werden.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;
using System.Linq;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Define an arrays of colors.
            string[] colors =
                new string[] { "Red", "Green", "Blue" };

            // Get all colors whose name starts with R in
            // reversed alphabetical order, and limit the
            // result set to the name and length of the
            // colors.
            var result =
                from c in colors
                where c.StartsWith("R")
                orderby c descending
                select new { Name = c, c.Length };

            // Print all colors to the console.
            foreach (var color in result)
            {
                Console.WriteLine(
                    color.Name + " (" + color.Length + ")");
            }
        }
    }
}
Spätestens an dieser Stelle wird deutlich, warum der Datentyp bei Linq nie spezifisch, sondern in der Regel mit dem Schlüsselwort var definiert wird. Ändert sich die Auswahl der in der Ergebnismenge enthaltenen Eigenschaften, so müssen die verwendeten Typen nicht angepasst werden.

Mit Linq ist es jedoch nicht nur möglich, einzelne Elemente in einer Ergebnismenge zusammenzufassen, zusätzlich kann diese Menge ihrerseits gruppiert werden. Dazu dient das Schlüsselwort group, das immer in Kombination mit dem Schlüsselwort by verwendet werden muss, und das angibt, welche Elemente wie gruppiert werden sollen. Wird group innerhalb einer Abfrage verwendet, so kann diese Abfrage kein select enthalten.

Eine auf diese Art erzeugte Gruppe von Elementen enthält das Kriterium, mit dessen Hilfe sie erstellt wurde, in der Eigenschaft Key und kann ihrerseits wiederum als Quelle für eine Schleife oder eine weitere Abfrage dienen. Im folgenden Beispiel werden jeweils all jene Elemente zu einer Gruppe zusammengefasst, deren Namen die gleiche Länge haben.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System;
using System.Linq;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Define an array of colors.
            string[] colors =
                new string[] { "Red", "Green", "Blue" };

            // Get all colors ordered alphabetically and put
            // them in groups depending on the length of the
            // color's name.
            var result =
                from c in colors
                orderby c
                group c by c.Length;

            // Iterate over all groups.
            foreach (var group in result)
            {
                // Print the group's key to the console.
                Console.WriteLine(group.Key);

                // Iterate over all colors within the group.
                foreach (var color in group)
                {
                    // Print the color to the console.
                    Console.WriteLine(color);
                }
            }
        }
    }
}
Neben den Möglichkeiten, die sich direkt aus der Verwendung solcher Abfragen ergeben, erweitert Linq sämtliche Aufzählungstypen mit Hilfe von Erweiterungsmethoden um weitere Methoden zur Manipulation der Ergebnismenge.

Soll beispielsweise nur das erste Element einer Ergebnismenge ausgewertet werden, so kann dies mit Hilfe der Methode First abgerufen werden.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Linq;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Define an array of colors.
            string[] colors =
                new string[] { "Red", "Green", "Blue" };

            // Get all colors ordered alphabetically.
            var result =
                from c in colors
                orderby c
                select c;

            // Get the first color from the result.
            string color = result.First();
        }
    }
}
Ebenso kann eine gewisse Anzahl an ersten Elementen abgerufen werden, wozu die Methode Take dient. Im folgenden Beispiel werden immer nur die ersten beiden Elemente der Ergebnismenge zurückgegeben, unabhängig davon, wie viele Elemente tatsächlich enthalten sind.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Linq;

namespace GoloRoden.GuideToCSharp
{
    /// <summary>
    /// Represents the application class.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Executes the application.
        /// </summary>
        public static void Main()
        {
            // Define an array of colors.
            string[] colors =
                new string[] { "Red", "Green", "Blue" };

            // Get all colors ordered alphabetically.
            var result =
                from c in colors
                orderby c
                select c;

            // Get the two top colors from the result.
            var topColors = result.Take(2);
        }
    }
}

Lambdaausdrücke

Intern werden in Linq geschriebene Abfragen in Aufrufe von Erweiterungsmethoden und Lambdaausdrücke umgewandelt. Dies geschieht ähnlich wie bei der foreach-Schleife im Hintergrund durch den Compiler, ohne dass der Entwickler dies bemerkt.

Beispielsweise wird die Abfrage
C#
1
2
3
4
5
6
7
// Get all colors whose name starts with an R in reversed
// alphabetical order.
var result =
    from c in colors
    where c.StartsWith("R")
    orderby c descending
    select c;
von C# in
C#
1
2
3
4
5
// Get all colors whose name starts with an R in reversed
// alphabetical order.
var result =
    colors.Where(c => c.StartsWith("R"))
    .OrderByDescending(c => c).Select(c => c);
umgewandelt.