LINQ står for Language Integrated Query og gør det muligt at søge, filtrere, sortere, gruppere og transformere data direkte i C#.
Du kan bruge LINQ på blandt andet:
List<T>Dictionary<TKey, TValue>LINQ er især vigtigt, fordi mange programmer handler om at tage en samling data og stille spørgsmål til den:
Forestil dig, at vi har en liste med tal og vil finde alle lige tal.
List<int> tal = new() { 1, 2, 3, 4, 5, 6 };
List<int> ligeTal = new();
foreach (int t in tal)
{
if (t % 2 == 0)
{
ligeTal.Add(t);
}
}
foreach (int t in ligeTal)
{
Console.WriteLine(t);
}
Det virker fint, men vi skal selv:
ifusing System.Linq;
List<int> tal = new() { 1, 2, 3, 4, 5, 6 };
var ligeTal = tal.Where(t => t % 2 == 0);
foreach (int t in ligeTal)
{
Console.WriteLine(t);
}
Where betyder: behold kun de elementer, hvor betingelsen er sand.
LINQ giver:
LINQ erstatter ikke loops. Loops er stadig vigtige. LINQ er bare ofte bedre, når målet er at spørge ind til data.
using System.Linq;I mange moderne C#-projekter er System.Linq allerede importeret globalt, men hvis metoder som Where, Select og OrderBy ikke findes, så mangler du typisk:
using System.Linq;
LINQ bruger ofte lambda-udtryk.
t => t % 2 == 0
Det kan læses som:
Tag et element
t, og returnér omt % 2 == 0.
Et lambda-udtryk har typisk denne form:
parameter => resultat
Eksempler:
tal.Where(t => t > 10);
navne.Where(n => n.StartsWith("A"));
produkter.Select(p => p.Navn);
personer.OrderBy(p => p.Alder);
Den mest brugte LINQ-stil i moderne C# er method syntax.
var resultat = tal
.Where(t => t > 3)
.OrderBy(t => t)
.Select(t => t * 2);
Man læser kæden oppefra og ned:
talLINQ kan også skrives i en SQL-lignende stil.
var resultat =
from t in tal
where t > 3
orderby t
select t * 2;
Begge versioner kan give samme resultat. I almindelig C#-kode vil du ofte se method syntax mest.
Mange LINQ-eksempler bliver nemmere at forstå med objekter.
public class Produkt
{
public string Navn { get; set; } = "";
public string Kategori { get; set; } = "";
public decimal Pris { get; set; }
public int Lager { get; set; }
}
List<Produkt> produkter = new()
{
new Produkt { Navn = "Tastatur", Kategori = "IT", Pris = 399, Lager = 8 },
new Produkt { Navn = "Mus", Kategori = "IT", Pris = 149, Lager = 20 },
new Produkt { Navn = "Skærm", Kategori = "IT", Pris = 1299, Lager = 4 },
new Produkt { Navn = "Kaffe", Kategori = "Kantine", Pris = 45, Lager = 50 },
new Produkt { Navn = "Notesbog", Kategori = "Kontor", Pris = 29, Lager = 100 },
new Produkt { Navn = "Whiteboard", Kategori = "Kontor", Pris = 699, Lager = 2 }
};
Where - filtreringWhere bruges til at vælge de elementer, der matcher en betingelse.
var dyreProdukter = produkter.Where(p => p.Pris > 500);
foreach (Produkt produkt in dyreProdukter)
{
Console.WriteLine($"{produkt.Navn}: {produkt.Pris} kr.");
}
Flere betingelser kan kombineres med && og ||.
var dyreItProdukter = produkter
.Where(p => p.Kategori == "IT" && p.Pris > 500);
Select - vælg eller omform dataSelect bruges til at lave hvert element om til noget andet.
var produktNavne = produkter.Select(p => p.Navn);
Du kan også lave nye anonyme objekter:
var visningsData = produkter.Select(p => new
{
p.Navn,
PrisTekst = $"{p.Pris} kr.",
ErPåLager = p.Lager > 0
});
Det er smart, når du kun skal bruge noget af dataen.
OrderBy og ThenBy - sorteringvar sorteretEfterPris = produkter.OrderBy(p => p.Pris);
Faldende sortering:
var dyresteFørst = produkter.OrderByDescending(p => p.Pris);
Sortering på flere felter:
var sorteret = produkter
.OrderBy(p => p.Kategori)
.ThenBy(p => p.Pris);
Her sorteres først på kategori og derefter på pris inden for hver kategori.
First, FirstOrDefault, Single og SingleOrDefaultFirst henter det første element, der matcher.
Produkt førsteItProdukt = produkter.First(p => p.Kategori == "IT");
Hvis der ikke findes et match, kaster First en fejl. Derfor er FirstOrDefault ofte sikrere.
Produkt? fundet = produkter.FirstOrDefault(p => p.Navn == "Printer");
if (fundet is null)
{
Console.WriteLine("Produktet blev ikke fundet.");
}
else
{
Console.WriteLine(fundet.Navn);
}
Single betyder: der skal være præcis ét match.
Produkt produkt = produkter.Single(p => p.Navn == "Mus");
Brug Single, når det er en fejl, hvis der findes nul eller flere resultater. Brug First, når du bare vil have det første.
Any og AllAny tjekker, om mindst ét element matcher.
bool findesDyreProdukter = produkter.Any(p => p.Pris > 1000);
All tjekker, om alle elementer matcher.
bool alleErPåLager = produkter.All(p => p.Lager > 0);
Any er også en god måde at tjekke, om en liste indeholder noget:
if (produkter.Any())
{
Console.WriteLine("Der er produkter i listen.");
}
Count, Sum, Average, Min og MaxLINQ kan lave simple beregninger.
int antalProdukter = produkter.Count();
int antalItProdukter = produkter.Count(p => p.Kategori == "IT");
decimal samletVærdi = produkter.Sum(p => p.Pris * p.Lager);
decimal gennemsnitsPris = produkter.Average(p => p.Pris);
decimal billigstePris = produkter.Min(p => p.Pris);
decimal dyrestePris = produkter.Max(p => p.Pris);
Pas på Average, Min og Max på tomme lister. Det giver fejl.
var tomListe = new List<int>();
if (tomListe.Any())
{
Console.WriteLine(tomListe.Average());
}
GroupBy - grupperingGroupBy bruges til at samle data i grupper.
var grupper = produkter.GroupBy(p => p.Kategori);
foreach (var gruppe in grupper)
{
Console.WriteLine(gruppe.Key);
foreach (Produkt produkt in gruppe)
{
Console.WriteLine($"- {produkt.Navn}");
}
}
gruppe.Key er værdien, der grupperes på. Her er det kategorien.
Du kan også lave en opsummering pr. gruppe:
var kategoriOversigt = produkter
.GroupBy(p => p.Kategori)
.Select(g => new
{
Kategori = g.Key,
AntalProdukter = g.Count(),
SamletLagerværdi = g.Sum(p => p.Pris * p.Lager)
});
Distinct - fjern dubletterPå simple typer er Distinct nemt.
List<string> kategorier = produkter
.Select(p => p.Kategori)
.Distinct()
.ToList();
På objekter kræver dubletlogik ofte, at man vælger et bestemt felt først, som i eksemplet ovenfor.
ContainsContains tjekker, om en samling indeholder en værdi.
List<string> tilladteKategorier = new() { "IT", "Kontor" };
var udvalgteProdukter = produkter
.Where(p => tilladteKategorier.Contains(p.Kategori));
Det er nyttigt, når brugeren vælger flere filtre.
Skip og TakeSkip springer elementer over. Take tager et bestemt antal.
var førsteSide = produkter
.OrderBy(p => p.Navn)
.Skip(0)
.Take(3);
var andenSide = produkter
.OrderBy(p => p.Navn)
.Skip(3)
.Take(3);
Det bruges ofte til paging, altså side 1, side 2, side 3 osv.
ToList og ToArrayMange LINQ-metoder returnerer ikke en List<T>, men en query, der kan itereres over.
Hvis du vil gemme resultatet som en liste:
List<Produkt> itProdukter = produkter
.Where(p => p.Kategori == "IT")
.ToList();
Hvis du vil have et array:
Produkt[] itProdukterArray = produkter
.Where(p => p.Kategori == "IT")
.ToArray();
Brug ToList, når du faktisk har brug for en liste. Kald det ikke automatisk efter hver LINQ-metode.
Mange LINQ-queries bliver først udført, når du itererer over resultatet eller kalder metoder som ToList, Count, First eller Any.
List<int> tal = new() { 1, 2, 3 };
var query = tal.Where(t => t > 1);
tal.Add(4);
foreach (int t in query)
{
Console.WriteLine(t);
}
Output:
2
3
4
Det kan overraske. Queryen blev defineret før 4 blev tilføjet, men den blev først udført i foreach.
Hvis du vil låse resultatet fast med det samme, så brug ToList.
var liste = tal.Where(t => t > 1).ToList();
Dette sorterer ikke selve listen:
var sorteret = produkter.OrderBy(p => p.Pris);
sorteret er en ny sorteret sekvens. produkter ligger stadig i samme rækkefølge.
Hvis du vil gemme sorteringen:
produkter = produkter
.OrderBy(p => p.Pris)
.ToList();
En typisk LINQ-kæde kan se sådan ud:
var resultat = produkter
.Where(p => p.Lager > 0)
.Where(p => p.Pris >= 100)
.OrderBy(p => p.Kategori)
.ThenByDescending(p => p.Pris)
.Select(p => new
{
p.Navn,
p.Kategori,
p.Pris
})
.ToList();
Det er ofte bedre at have flere læsbare trin end at presse alt ind i én meget lang linje.
LINQ virker også på tekst, fordi en string kan behandles som en samling af tegn.
string tekst = "programmering";
var vokaler = tekst
.Where(tegn => "aeiouyæøå".Contains(char.ToLower(tegn)))
.ToList();
Console.WriteLine(vokaler.Count);
Du kan også bruge LINQ på en liste af navne:
List<string> navne = new() { "Anna", "Bo", "Asta", "Carl", "Amir" };
var navneMedA = navne
.Where(n => n.StartsWith("A"))
.OrderBy(n => n);
Hvis du har læst en fil ind som linjer, kan LINQ bruges til at filtrere og analysere dem.
string[] linjer = File.ReadAllLines("log.txt");
var fejlLinjer = linjer
.Where(linje => linje.Contains("ERROR"))
.ToList();
Du kan også tælle:
int antalAdvarsler = linjer.Count(linje => linje.Contains("WARNING"));
Dictionary og LINQEn dictionary kan også bruges med LINQ.
Dictionary<string, int> point = new()
{
["Anna"] = 12,
["Bo"] = 7,
["Clara"] = 10
};
var topElever = point
.Where(pair => pair.Value >= 10)
.OrderByDescending(pair => pair.Value);
foreach (var elev in topElever)
{
Console.WriteLine($"{elev.Key}: {elev.Value}");
}
I en dictionary er hvert element et key/value-par.
Lav et lille konsolprogram, hvor brugeren kan vælge en kategori og en minimumspris.
Console.Write("Kategori: ");
string kategori = Console.ReadLine() ?? "";
Console.Write("Minimumspris: ");
decimal minimumspris = decimal.Parse(Console.ReadLine() ?? "0");
var fundneProdukter = produkter
.Where(p => p.Kategori.Equals(kategori, StringComparison.OrdinalIgnoreCase))
.Where(p => p.Pris >= minimumspris)
.OrderBy(p => p.Pris)
.ToList();
if (!fundneProdukter.Any())
{
Console.WriteLine("Ingen produkter fundet.");
}
else
{
foreach (Produkt produkt in fundneProdukter)
{
Console.WriteLine($"{produkt.Navn} - {produkt.Pris} kr.");
}
}
Udvidelser:
using System.Linq;First() uden at være sikker på, at der findes et elementSingle() på data, hvor der måske findes flere matchesToList() for tidligt og for ofteOrderBy() ændrer den oprindelige liste| Metode | Bruges til | Eksempel |
|---|---|---|
Where |
Filtrere | tal.Where(t => t > 5) |
Select |
Omforme/vælge data | personer.Select(p => p.Navn) |
OrderBy |
Sortere stigende | produkter.OrderBy(p => p.Pris) |
OrderByDescending |
Sortere faldende | produkter.OrderByDescending(p => p.Pris) |
ThenBy |
Ekstra sortering | .ThenBy(p => p.Navn) |
First |
Første element, fejl hvis intet findes | tal.First(t => t > 5) |
FirstOrDefault |
Første element eller standardværdi | personer.FirstOrDefault(p => p.Id == 1) |
Any |
Findes der mindst ét? | produkter.Any(p => p.Lager == 0) |
All |
Matcher alle? | produkter.All(p => p.Pris > 0) |
Count |
Antal | produkter.Count() |
Sum |
Sum | produkter.Sum(p => p.Pris) |
Average |
Gennemsnit | produkter.Average(p => p.Pris) |
Min |
Mindste værdi | produkter.Min(p => p.Pris) |
Max |
Største værdi | produkter.Max(p => p.Pris) |
GroupBy |
Gruppere | produkter.GroupBy(p => p.Kategori) |
Distinct |
Fjerne dubletter | navne.Distinct() |
Contains |
Indeholder værdi | ids.Contains(p.Id) |
Skip |
Spring over | .Skip(10) |
Take |
Tag et antal | .Take(10) |
ToList |
Lav resultat til liste | .ToList() |
ToArray |
Lav resultat til array | .ToArray() |
Lav en liste med tallene 1 til 20.
Find med LINQ:
Lav en liste med mindst 8 navne.
Find med LINQ:
AeLav en liste af produkter med Navn, Pris og Kategori.
Find med LINQ:
Lav en klasse:
public class Elev
{
public string Navn { get; set; } = "";
public int Karakter { get; set; }
public bool HarAfleveret { get; set; }
}
Lav en liste med mindst 6 elever.
Find med LINQ:
Lav en tekstfil eller en liste af strings med loglinjer:
INFO Program startet
WARNING Lav diskplads
ERROR Kunne ikke gemme fil
INFO Program afsluttet
Find med LINQ:
ERRORWARNINGINFO-linjerContains.Skip og Take.GroupBy.var produkterOver100 = produkter
.Where(p => p.Pris > 100)
.ToList();
var sorteretEfterPris = produkter
.OrderBy(p => p.Pris)
.ToList();
var produktNavne = produkter
.Select(p => p.Navn)
.ToList();
decimal samletPris = produkter.Sum(p => p.Pris);
Produkt dyreste = produkter
.OrderByDescending(p => p.Pris)
.First();
var grupperetEfterKategori = produkter
.GroupBy(p => p.Kategori);
Brug gerne et almindeligt loop, hvis:
Brug LINQ, hvis:
LINQ handler om at stille spørgsmål til data.
De vigtigste metoder at kunne først er:
WhereSelectOrderByFirstOrDefaultAnyCountGroupByToListNår du kan dem, kan du løse rigtig mange hverdagsproblemer i C#.