Gå til indholdet

Grundlæggende LINQ

LINQ er en af de features, du som udvikler vil få stor glæde af, men syntaks og teori kan virke lidt skræmmende, hvis du ikke har den helt store erfaring med C#. Det skyldes især brugen af funktionsorienteret kode og lambda-udtryk. Men LINQ gør det muligt at skrive meget avanceret kode på meget få linjer og sikrer samtidig hurtig afvikling – så det er vigtigt, du bruger tid på at forstå, hvordan LINQ fungerer.

Information til undervisere

LINQ er super effektivt og vigtigt at kende til i forbindelse med C#. Det er dog vigtigt, at du som underviser sørger for, at dine elever har en god forståelse for delegates og lambda-udtryk, inden de kaster sig ud i LINQ. Det er også vigtigt, at du sørger for, at eleverne forstår, at LINQ er en del af C# og ikke en selvstændig teknologi.

Husk også at nævne, at LINQ to Objects er en del af .NET og at der findes andre versioner af LINQ, som eksempelvis LINQ to XML og især LINQ to Entities.

Du må også gerne lige nævne, at selv om LINQ kan gøre livet meget nemmere så er der altså noget overhead i forbindelse med afvikling af LINQ-udtryk, samt faldgrupper i forbindelse med sen afvikling. Performance er altså ikke altid bedre med LINQ.

LINQ står for Language INtegrated Query, og giver overordnet en nem mulighed for at gennemløbe, filtrere, sortere og gruppere samlinger af data i arrays, lister og andre typer. Det er ligegyldigt, om der er tale om samlinger af simple strukturer som heltal eller komplekse samlinger af indbyggede eller egne objekter. I langt de fleste tilfælde vil du kunne benytte LINQ til at behandle samlingerne på mange forskellige måder.

Hvorfor LINQ

Bare for at du får en lille ide om, hvad LINQ stiller til rådighed så forestil dig en eksamensopgave, du skal løse, der lyder som følger: Skriv en kode, som fra liste af heltal, samt et heltal der repræsenterer en maksimal værdi, finder en ny liste af tal, der består af unikke tal i sorteret form, og som er under den maksimale værdi. Som eksempel skal listen 2, 5, 5, 7, 3 returnere 2, 3, 5, hvis den maksimale værdi er 5.

En sådan opgave kunne en studerende uden kendskab til LINQ måske løse således:

using System.Collections.Generic;

namespace Demo
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            List<int> tal = new List<int> { 2, 5, 5, 7, 3 };
            List<int> res = FindUnikkeTal(tal, 5);  // 2, 3, 5
        }

        private static List<int> FindUnikkeTal(List<int> tal, int max)
        {
            List<int> tmp = new List<int>();
            for (int i = 0; i < tal.Count; i++)
            {
                if (tal[i] <= max && !tmp.Contains(tal[i]))
                    tmp.Add(tal[i]);
            }
            tmp.Sort();
            return tmp;
        }
    }
}

Det kræver lidt kode til at gennemløbe, filtrere og sortere listen af tal, men løser jo opgaven.

En studerende med kendskab til LINQ ville måske løse opgaven således:

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

namespace Demo
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            List<int> tal = new List<int> { 2, 5, 5, 7, 3 };
            List<int> res = tal.Where(i => i <= 5)
                .OrderBy(i => i).Distinct().ToList();   // 2, 3, 5
        }
    }
}

Hele filtreringen og sorteringen kan skrives på én linjes kode, men det kræver jo lidt viden om LINQ, delegates og lambda-udtryk.

Info

Hvis du gerne vil have en nem samling af objekter at “lege” med i LINQ kan du bruge objekter fra FileInfo. Følgende kode skaber et array af FileInfo-objekter fra en mappe:

// Skab en liste af filer i en mappe vi kan lege med
var mappe = new System.IO.DirectoryInfo(@"c:\temp");
var filer = mappe.GetFiles("*.*", System.IO.SearchOption.AllDirectories);
Console.WriteLine($"Har fundet {filer.Length} i {mappe.Name}");

Nu kan filer bruges til at teste de forskelige LINQ metoder.

Om LINQ

Hvis du søger på nettet efter LINQ, vil du muligvis finde flere versioner af teknologien, fordi den benyttes flere steder – eksempelvis i Entity Framework (kommunikation med databaser ved generering af SQL) og ved XML-serialisering.

Men den version, der er indbygget i C#, hedder LINQ to Objects og benyttes til at arbejde på samlinger af objekter af alle typer i hukommelsen. Du behøver altså ikke tilføje eksterne komponenter for at kunne benytte LINQ, og du kan benytte en almindelig .NET konsolapplikation til at afprøve dette kapitels eksempler.

Det eneste, du skal være opmærksom på, er, at LINQ primært fungerer ved hjælp af såkaldte extensionsmetoder. Det er metoder, som kan tilføjes eksisterende klasser, så de er nemme at finde og tilgå. Da LINQ arbejder på samlinger, er klasser som System.Array, List med flere udvidet med nye metoder, men for at disse kan tilgås, skal du tilføje System.Linq-namespacet som en using-instruktion. Det behøver du dog ikke fra og med .NET 5.

Info

For yderligere info se også aloisdg/awesome-linq

Syntaks

LINQ-udtryk kan skrives på to måder.

Enten kan du vælge at skrive udtryk i en SQL-lignende syntaks kaldet query syntax, eller benytte en funktionsorienteret syntaks kaldet method syntax. Sidstnævnte er den mest benyttede og i øvrigt også den nemmeste at læse og skrive, når du kender til delegates og lambda-udtryk, men du kan vælge den syntaks, der passer dig bedst. I dette kapitel vil du dog kun se den mest benyttede (metode) syntaks, men for en god ordens skyld er her brugen af begge syntakser til sammenligning.

Her er et simpelt udtryk vist i query syntaks:

List<int> tal = new List<int> { 3, 5, 5, 7, 2 };
var res = from t in tal where t < 5 orderby t select t;

og her er det samme udtryk vist i method syntaks:

List<int> tal = new List<int> { 3, 5, 5, 7, 2 };
var res = tal.Where(i => i < 5).OrderBy(i => i);

Begge udtryk kan afvikles til resultatet ”2, 3” og kan læses som “find alle tal mindre end 5 i sorteret form” men der er jo stor forskel i syntaksen.

Det første udtryk ligner SQL (Structured Query Language) bortset fra, at select-kodeordet er placeret til sidst. Det kan du vælge at bruge, hvis du ikke er bekendt med den funktionsorienterede tilgang til programmering.

Det sidste udtryk består af genkendelige metodekald, og de fleste metoder returnerer et nyt udtryk, som efterfølgende metoder kan arbejde videre på. Det gør koden meget kompakt og effektiv.

Som det fremgår, benytter syntaksen lambda-udtryk, og her skal du huske på reglerne jævnfør kapitlet om Delegates – du behøver ikke angive typer på argumenter og parenteser er ikke nødvendige, hvis der kun er ét argument. Hvis der kun er én instruktion, og denne returnerer en værdi, behøver du ikke angive tuborgklammer eller return-kodeordet. Så i virkeligheden kan følgende:

var res = tal.Where(i => i < 5).OrderBy(i => i);

skrives som:

var res = tal.Where((int i) => { return i < 5; }).OrderBy((int i) => { return i; });

eller for den sags skyld som:

Func<int, bool> w = i => i < 5;
Func<int, int> o = i => i;
var res = tal.Where(w).OrderBy(o);

eller pindet helt ud:

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

namespace Demo
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            List<int> tal = new List<int> { 2, 5, 5, 7, 3 };
            Func<int, bool> w = Find;
            Func<int, int> o = Sort;
            var res = tal.Where(w).OrderBy(o);
        }

        static bool Find(int i)
        {
            return i < 5;
        }

        static int Sort(int i)
        {
            return i;
        }
    }
}

Pointen er, at både Where og OrderBy-metoderne tager en reference til et delegate-objekt som argument, og kalder metoder fra denne ved afvikling af udtrykket. Hvordan du skaber delegate-objekterne, er helt op til dig, men som du sikkert har luret, er den forkortede anonyme lambda-version det nemmeste:

var res = tal.Where(i => i < 5).OrderBy(i => i);

Det er hurtigt at både læse og skrive.

Sen afvikling

Når du skriver et LINQ-udtryk, er det reelt et udtryk. For at finde et resultat, skal udtrykket afvikles, og det sker automatisk, når du eksempelvis foretager et gennemløb:

List<int> tal = new List<int> { 2, 5, 5, 7, 3 };
var res = tal.Where(i => i < 5).OrderBy(i => i);    
// Endnu ikke afviklet

foreach (var t in res)  // HER bliver det afviklet
    Console.WriteLine(t);

Det kan virke lidt uforståeligt, men det, at der arbejdes med udtryk, giver en masse fordele relateret til datafriskhed, performance, hukommelsesforbrug samt mulighed for at andre teknologier kan pille et udtryk fra hinanden og kigge på de enkelte elementer. Det er blandt andet det, som LINQ to EF (Entity Framework) benytter til at skabe SQL kald mod en database ud fra LINQ-udtryk.

Det er måske ikke noget, du vil opdage, medmindre du i debuggeren vil se resultatet af et LINQ-udtryk, men her er det tydeligt, at LINQ ikke afvikler udtrykket, før du reelt beder om resultatet:

LINQ i debuggeren

Du vil også se det, når du kigger nærmere på returtypen af et LINQ-udtryk. Der kommer ikke en List retur i førnævnte eksempel, men en liste af en speciel LINQ-type. Du vil dog tit se, at udviklere gennemtvinger en afvikling og dermed en returværdi af en konkret og kendt type. Det kan ske ved eksempelvis at kalde metoderne ToList eller ToArray:

var res = tal.Where(i => i < 5).OrderBy(i => i).ToList();   

Nu er udtrykket både skabt og afviklet, og variablen res er af typen List.

Men nogle gange giver det at arbejde med udtryk en masse muligheder – også relateret til genbrug:

List<int> tal = new List<int> { 2, 5, 5, 7, 3 };

var u = tal.Where(i => i < 5);
var res1 = u.OrderBy(i => i);
var res2 = u.OrderByDescending(i => i);

Her benyttes Where-udtrykket flere gange.

Til orientering er sen afvikling oversat fra defered execution. Så har du nemmere ved at finde yderligere information på nettet, når du søger information om LINQ.

Filtrering

Når du arbejder med en samling af data, er filtrering nok en af de operationer, du har mest brug for, og her er Where-metoden tilgængelig. Den tager som argument en reference til en delegate svarende til en såkaldt predicate. Det er en metode, der returnerer sand eller falsk, og som tager et enkelt argument svarende til typen på samlingen – altså en Func (der findes også en Predicate, men den benyttes ikke så meget i nyere C# kode). Når LINQ gennemløber din samling, vil den for hvert element kalde din metode, og hvis metoden returnerer sand, kommer elementet med i resultatet – ellers forkastes det:

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

namespace Demo
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            List<int> tal = new List<int> { 2, 5, 5, 4, 7, 3, 8, 1 };
            List<int> res;

            res = tal.Where(i => true).ToList();                
            // alle
            Console.WriteLine(string.Join(' ', res));           
            // 2 5 5 4 7 3 8 1

            res = tal.Where(i => false).ToList();               
            // ingen
            Console.WriteLine(string.Join(' ', res));           
            // 

            res = tal.Where(i => i < 7).ToList();               
            // < 7
            Console.WriteLine(string.Join(' ', res));           
            // 2 5 5 4 3 1

            res = tal.Where(i => i < 7 && i % 2 == 0).ToList(); 
            // < 7 og lige tal
            Console.WriteLine(string.Join(' ', res));           
            // 2 4

            res = tal.Where(i => ErPrimtal(i)).ToList();        
            // primtal
            Console.WriteLine(string.Join(' ', res));           
            // 2 5 5 7 3

            res = tal.Where(ErPrimtal).ToList();                
            // primtal (igen – mere simpel syntaks)
            Console.WriteLine(string.Join(' ', res));           
            // 2 5 5 7 3

        }

        static bool ErPrimtal(int nummer)
        {
            if (nummer <= 1) return false;
            if (nummer == 2) return true;
            if (nummer % 2 == 0) return false;

            var til = (int)Math.Floor(Math.Sqrt(nummer));
            for (int tæller = 3; tæller <= til; tæller += 2)
                if (nummer % tæller == 0)
                    return false;

            return true;
        }
    }
}

Som du kan se, kan du kode hvad som helst i metoden – den skal bare returnere sand eller falsk, og det behøver sågar ikke have noget med det argument, metoden kaldes med, at gøre. Det er vigtigt at forstå, at LINQ arbejder med alle mulige typer af instanser – herunder også instanser af objekter fra frameworket eller dine egne. For nemt at kunne lege med LINQ på et mere komplekst objekt, kan du prøve følgende:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);

FileInfo-klassen kommer fra System.IO og repræsenterer en fil på disken. Ovennævnte kode vil skabe et array af FileInfo-objekter fra en mappe og dens underbiblioteker. De enkelte FileInfo-objekter har egenskaber som eksempelvis Name (string), FullName (string), Length (long), Exists (bool), Extension (string), CreationTime (DateTime) og mange flere. Et array af disse egner sig derfor godt til at prøve forskellige LINQ-metoder.

Hvad med eksempelvis:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);
FileInfo[] res;

// Alle filer under 1000 bytes
res = filer.Where(i => i.Length < 1000).ToArray();

// Alle txt filer
res = filer.Where(i => i.Extension == ".txt").ToArray();

// Alle readonly txt filer
res = filer.Where(i => i.Extension == ".txt" && i.IsReadOnly).ToArray();

// Alle filer hvor test indgår i navn
res = filer.Where(i => i.Name.Contains("test")).ToArray();

Bemærk, at typen T i Func nu er af typen FileInfo, og det er den, du kan spørge på i lambda-udtrykket. Hvis du kun ønsker en fil ud af et udtryk, kan du bruge en af de mange singleton-metoder som eksempelvis First eller FirstOrDefault. Sidstnævnte returnerer en reference til et objekt eller null:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);
FileInfo res;

// Første fil der indeholder "test" i navnet
res = filer.FirstOrDefault(i => i.Name.Contains("test"));
if (res != null)
    Console.WriteLine(res.FullName);

// Første txt fil under 1000 bytes
res = filer.FirstOrDefault(i => i.Extension == ".txt" 
    && i.Length < 1000);
if (res != null)
    Console.WriteLine(res.FullName);

Bemærk, at der nu returneres et enkelt objekt og ikke et array af objekter.

Metoden kan i øvrigt også kaldes uden argumenter direkte på resultatet af et udtryk:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);
FileInfo res;

// Første fil under 1000 bytes
res = filer.Where(i => i.Length < 1000).FirstOrDefault();

Der findes en del andre metoder, som returnerer et enkelt element, men FirstOrDefault er meget brugt. Du skal bare selv huske at kontrol-lere for en null værdi, som fortæller, at der ikke kunne findes nogle elementer.

Sortering

Sortering af data er naturligvis også meget brugt, og til det kan du benytte metoderne:

  • OrderBy
  • OrderByDescending
  • ThenBy
  • ThenByDescending

Metoderne tager som argument en Func, hvor T er typen og TKey er den egenskab, der ønskes sorteret efter.

Her er et par eksempler relateret til heltal, hvor TKey blot er værdien selv:

List<int> tal = new List<int> { 2, 5, 5, 7, 3 };
List<int> res;
res = tal.OrderBy(i => i).ToList();
res = tal.OrderByDescending(i => i).ToList();

Blot til orientering kan TKey jo være hvad som helst. Her sorteres efter et tilfældigt tal – svarende til at listen ”blandes”:

List<int> tal = new List<int> { 2, 5, 5, 7, 3 };
List<int> res;
Random rnd = new Random();
res = tal.OrderBy(i => rnd.Next(1, 1000)).ToList();

Sorteringsmetoderne kan naturligvis også benyttes på objekter – her eksempler på brug ved arrays af filer:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);

FileInfo[] res;

res = filer.OrderBy(i => i.Name).ToArray();
res = filer.OrderBy(i=>i.Length).ToArray();
res = filer.OrderBy(i => i.Extension).ThenBy(i => i.Name).ToArray();
res = filer.OrderByDescending(i => i.CreationTime).ThenBy(i => i.Name).ToArray();

Igen skal du huske, at TKey kan være hvad som helst.

Gruppering

Gruppering af data er ret komplekst, hvis du selv skal kode det, men metoden GroupBy gør det nemt. Den tager en Func som argument, hvor TKey er den værdi, der skal grupperes ud fra, og den returnerer grupperede samlinger med en nøgle (Key), der repræsenterer den grupperede værdi:

List<int> tal = new List<int> { 2, 5, 5, 7, 3, 2 };

var res = tal.GroupBy(i => i);
/*
    res er nu en samling af gruppede samlinger af heltal
    og hver samling har en key
    [2, 2] key = 2
    [5, 5] key = 5
    [7]    key = 7
    [3]    key = 3
*/

Du kan løbe samlingen af samlinger igennem på flere måder – men her er den nemmeste:

List<int> tal = new List<int> { 2, 5, 5, 7, 3, 2 };
var res = tal.GroupBy(i => i);
foreach (var gruppe in res)
{
    Console.WriteLine(gruppe.Key);
    foreach (var t in gruppe)
    {
        Console.WriteLine("\t" + t);
    }
}
/*
    2
            2
            2
    5
            5
            5
    7
            7
    3
            3
*/

Du kan naturligvis gruppere samlinger af komplekse objekter også – prøv følgende på din egen maskine:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);

var res = filer.OrderBy(i => i.FullName).GroupBy(i => i.Extension).ToArray();
foreach (var gruppe in res)
{ Console.WriteLine(gruppe.Key);
    foreach (var fil in filer)
    {
        Console.WriteLine($"\t{fil.Name} ({fil.Length})");
    }
}

I koden vil filer blive grupperet og udskrevet efter Extension.

GroupBy-metoden kan bruges til mange ting. Prøv eksempelvis at tænke på, hvordan den kan bruges til at gruppere en samling terninger efter værdi, og på den måde finde ud af, om der er yatzy, fuldt hus, to ens, tre ens og så videre.

Projektion

Der kan tit være behov for at ændre resultatet fra et LINQ udtryk. Det kunne eksempelvis være noget så simpelt som en generel ændring af værdierne eller typen. Her kan metoden Select hjælpe dig. Se eksempelvis følgende:

List<int> tal = new List<int> { 2, 5, 5, 7, 3, 2 };
List<int> plusEn = tal.Select(i => i + 1).ToList();
Console.WriteLine(string.Join(' ', plusEn)); // 3 6 6 8 4 3

Metoden Select tager som argument en Func (i dette tilfælde er T og TResult en int), og baseret på resultatet af denne metode skabes en ny liste. Det er underordnet hvad metoden gør, eller hvilken type der returneres:

List<int> tal = new List<int> { 2, 5, 5, 7, 3, 2 };
List<string> res = tal.Select(i => i.ToString().PadRight(3)).ToList();
Console.WriteLine(string.Join("", res)); // 2  5  5  7  3  2

Her returneres en liste af formaterede strenge.

Metoden Select kan også benyttes til at ændre typen på komplekse typer. I følgende eksempel findes alle filer i sorteret form fra en mappe, men der returneres ikke en liste af FileInfo-objekter, men i stedet en liste af en defineret type som kunne se således ud:

class MinFil {
    public string Navn { get; set; }
    public long Længde { get; set; }
}

Nu kan typen benyttes i Select-metoden som følger:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);
List<MinFil> res = filer.OrderBy(i => i.Name)
    .Select(i => new MinFil { Navn = i.Name, Længde = i.Length })
    .ToList();

Hvis du vælger at benytte en record i stedet:

record MinFil(string Navn, long Længde);

kan du skære lidt kode væk:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);
List<MinFil> res = filer.OrderBy(i => i.Name)
    .Select(i => new MinFil(i.Name, i.Length))
    .ToList();

Du kan også vælge at returnere en tuple og helt droppe type definition:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);
var res = filer.OrderBy(i => i.Name)
    .Select(i => (Navn: i.Name, Længde: i.Length))
    .ToList();

Eller du kan vælge at returnere et såkaldt anonymt objekt. Det ligger uden for rammerne i denne bog at beskrive anonyme typer dybere, men du kan se det lidt som en defineret tuple.

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);
var res = filer.OrderBy(i => i.Name)
    .Select(i => new { Navn = i.Name, Længde = i.Length })
    .ToList();

Nu består res af en liste af en anonym type med egenskaberne Navn (string) og Længde (long). Der er masser af information omkring anonyme typer i C# på nettet.

Begrænsning af resultat

Der findes en del metoder, som kan bruges til at begrænse resultatet af et udtryk.

Metoden First returnerer det første element, metoden Last det sidste, og metoden ElementAt returnerer et element ud fra et indeks. Der findes ligeledes metoder, som returnerer et element eller default-værdien – herunder FirstOrDefault og LastOrDefault:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);
var fil = filer.FirstOrDefault();
if (fil != null)
    Console.WriteLine(fil.FullName);

Her findes den første fil i arrayet, og hvis arrayet ikke har nogen elementer, returneres null (default for klasser og dermed FileInfo). Yderligere kan metoden Take returnere et antal, og metoden Skip vil springe elementer over. Især de sidste metoder er meget brugbare:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);

// Hopper over de første 10 og tager de næste 10
var res = filer.Skip(10).Take(10).ToArray();

Som du kan se, kan kombinationen Skip og Take være meget brugbar.

Metoder relateret til tal

Hvis du har med tal at gøre (enten en samling af tal eller egenskaber som tal) findes der nogle specielle metoder, der kan være ret smarte. Metoden Max finder den største værdi, Min den mindste, Sum en sum og Average finder gennemsnittet. Her eksempler på tal:

List<int> tal = new List<int> { 2, 5, 5, 7, 3, 2 };
Console.WriteLine(tal.Min());       // 2
Console.WriteLine(tal.Max());       // 7
Console.WriteLine(tal.Sum());       // 24
Console.WriteLine(tal.Average());   // 4

og her objekter:

FileInfo[] filer = new DirectoryInfo(@"c:\tmp")
    .GetFiles("*.*", SearchOption.AllDirectories);

Console.WriteLine(filer.Min(i=>i.Length));       
// Mindste fil størrelse
Console.WriteLine(filer.Max(i => i.Length));     
// Største fil størrelse
Console.WriteLine(filer.Average(i => i.Length)); 
// Gns af fil størrelse

LINQ og grupper

Der findes en del metoder de rer relateret til at sammenligne grupper:

  • Except: Except metoden i LINQ bruges til at returnere en sekvens, der indeholder de elementer fra den første samling, der ikke findes i den anden samling. Det er en nem måde at finde forskellene mellem to samlinger på. Dette er nyttigt, når du ønsker at identificere elementer, der er unikke for en bestemt samling.

  • Union: Union metoden bruges til at returnere en sekvens, der indeholder de unikke elementer fra begge samlinger. Det vil sige, at den returnerer alle de unikke elementer i den første og den anden samling. Det er en måde at kombinere data fra to samlinger på og samtidig sikre, at der ikke er nogen duplikater.

  • Intersect: Intersect metoden bruges til at returnere en sekvens, der kun indeholder de elementer, der findes i begge samlinger. Det er en effektiv måde at finde fælles elementer mellem to samlinger på.

  • Distinct: Distinct metoden bruges til at fjerne duplikater fra en samling. Den returnerer en ny sekvens, der kun indeholder de unikke elementer fra den originale samling. Det er især nyttigt, når du har en samling, hvor du kun er interesseret i de unikke elementer.

  • Concat: Concat metoden er en anden brugbar metode, der kombinerer to samlinger. I modsætning til Union metoden, fjerner Concat ikke duplikater. Den returnerer simpelthen en ny sekvens, der indeholder alle elementerne fra begge samlinger, i den rækkefølge de oprindeligt optrådte.

Disse metoder er værdifulde redskaber i LINQ, der giver dig mulighed for at manipulere og arbejde med samlinger på højt niveau, uden at skulle skrive komplekse loops og betingelseslogik.

Andre metoder

Aggregate

Aggregate-metoden i LINQ er en kraftfuld funktion, der kan bruges til at udføre en akkumulerende eller reducerende operation på en samling. Med Aggregate kan du for eksempel beregne summer, produkter, gennemsnit, eller endda foretage mere komplekse beregninger, der involverer alle elementerne i en samling. Her er et eksempel, der illustrerer brugen af Aggregate:

Forestil dig, at du har en liste med tal, og du ønsker at beregne produktet af alle disse tal (dvs. multiplicere dem alle sammen). Dette kan gøres effektivt ved hjælp af Aggregate-metoden:

List<int> tal = new List<int> { 1, 2, 3, 4, 5 };

// Brug af Aggregate til at beregne produktet
int produkt = tal.Aggregate(1, (akkumuleret, næste) => akkumuleret * næste);

Console.WriteLine($"Produktet af tallene er: {produkt}");

I dette eksempel starter vi med en initialværdi på 1 (da 1 er det neutrale element i multiplikation). Aggregate-metoden itererer derefter gennem listen, hvor akkumuleret repræsenterer det akkumulerede resultat indtil nu, og næste er det næste element i listen. For hvert element i listen multipliceres det akkumulerede resultat med dette element.

Outputtet af koden vil være:

Produktet af tallene er: 120

Dette resultat opnås ved at multiplicere 1 * 2 * 3 * 4 * 5.

Aggregate-metoden er ikke begrænset til simple matematiske operationer. Den kan også bruges til mere komplekse operationer, såsom at sammenkæde strenge, finde det største element under en bestemt betingelse eller endda til at aggregere data i mere komplekse datastrukturer.

For eksempel, hvis du har en liste af strenge, og du ønsker at sammenkæde dem adskilt af et komma, kan det også gøres med Aggregate:

List<string> ord = new List<string> { "Hej", "verden", "med", "LINQ" };
string sætning = ord.Aggregate((akkumuleret, næste) => akkumuleret + ", " + næste);

Console.WriteLine(sætning);

Dette vil give outputtet: “Hej, verden, med, LINQ”.

Aggregate er en alsidig metode, der kan tilpasses til mange forskellige scenarier og er et kraftfuldt værktøj i LINQ’s arsenal.

All / Any

Disse metoder giver mulighed for at tjekke, om alle eller nogle af elementerne i en samling opfylder et bestemt kriterium.

bool alleOver30 = personer.All(p => p.Alder > 30);
bool nogleOver30 = personer.Any(p => p.Alder > 30);

Contains

Contains-metoden bruges til at tjekke, om en samling indeholder et bestemt element.

int[] arr1 = { 1, 2, 3 };
bool contains = arr1.Contains(2); // true

SequenceEqual

SequenceEqual-metoden bruges til at sammenligne to sekvenser for at se, om de indeholder de samme elementer i samme rækkefølge.

int[] arr1 = { 1, 2, 3 };
int[] arr2 = { 1, 2, 3 };
bool result = arr1.SequenceEqual(arr2); // true

Opgaver

Er LINQ funktionsorienteret

Får tit spørgsmålet “Er LINQ et eksempel på FP (Funktionsorienteret Programmering)?”.

Svaret er at man godt kan opfatte LINQ som en funktionsorienteret tilgang inden for C# og .NET. LINQ udvider C# med funktioner fra funktionsorienteret programmering, og det gør det muligt at arbejde med data og samlinger på en mere deklarativ måde.

Funktionsorienteret programmering er en programmeringsparadigme, hvor man fokuserer på at bruge funktioner som grundlæggende byggesten i stedet for objekter og metoder i objektorienteret programmering (OOP). Funktioner i funktionsorienteret programmering er ofte “rene”, hvilket betyder, at de ikke har nogen sideeffekter og kun afhænger af deres inputparametre.

LINQ indfører flere funktionsorienterede koncepter og metoder i C#, såsom:

  • Højere ordens funktioner : Funktioner, der tager andre funktioner som argumenter, eller returnerer funktioner. For eksempel, LINQ-metoder som Select, Where, OrderBy og Aggregate tager funktioner som parametre og anvender dem på elementer i en samling.

  • Lambda expressions : Kompakte og anonyme funktioner, der kan bruges som argumenter i højere ordens funktioner. Lambda’er er en vigtig del af LINQ og gør det nemt at skrive komplekse forespørgsler med kort og læsbar kode.

  • Uforanderlighed (Immutability) : LINQ-metoder genererer nye samlinger som resultat og ændrer ikke de originale samlinger. Dette følger princippet om uforanderlighed, som er en central del af funktionsorienteret programmering.