What is a spread operator in C#?

Ny Spread-operator i C# 12: En Dybdegående Guide

25/08/2023

Rating: 4.81 (2369 votes)

Med lanceringen af .NET 8 og C# 12 er der kommet en række spændende nye funktioner til C#-sproget, og en af de mest bemærkelsesværdige er introduktionen af samlingsudtryk og det tilhørende spread-element (`..`). Denne funktion, som er stærkt inspireret af lignende funktionalitet i sprog som JavaScript, er designet til at modernisere og forenkle den måde, udviklere initialiserer og manipulerer samlinger på. Borte er dagene med omstændelige `Concat`, `AddRange` eller manuelle løkker for simple opgaver som at flette to lister. Nu kan du gøre det hele med en kortfattet og intuitiv syntaks, der gør din kode både renere og nemmere at forstå.

What is a spread element in C#?
In the specification, it is referred to in the language grammar as SpreadElement, though informally called the "spread syntax" since it is not a context-free grammar. C# 12 has added the spread feature. introw1 = [4, 5, 6]; introw2 = [7, 8, 9]; intsingle = [..row0, ..row1, ..row2]; foreach (var element in single)

I denne artikel vil vi dykke ned i, hvad samlingsudtryk og spread-operatoren er, hvordan de fungerer, og hvordan du kan bruge dem til at forbedre din daglige kodning. Vi vil se på praktiske eksempler, fra simpel initialisering af arrays til mere avancerede scenarier, hvor du kan gøre dine egne datatyper kompatible med denne nye syntaks.

Indholdsfortegnelse

Hvad er Samlingsudtryk?

Før vi kan tale om spread-operatoren, er det vigtigt at forstå det koncept, den er en del af: samlingsudtryk (collection expressions). Et samlingsudtryk er en ny, kortfattet syntaks til at oprette samlingsværdier i C#. Syntaksen bruger firkantede parenteser `[` og `]` til at omslutte en sekvens af elementer. Denne syntaks kan bruges til at initialisere mange forskellige typer af samlinger.

Lad os se på et simpelt eksempel. Tidligere ville du måske initialisere et array af strenge således:

string[] ugedage = new string[] { "Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn" };

Med samlingsudtryk i C# 12 kan det samme opnås meget mere elegant:

string[] ugedage = ["Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"];

Denne syntaks er ikke kun begrænset til arrays. Den kan bruges til at initialisere et væld af samlingstyper, herunder `List`, `Span`, `ImmutableArray` og mange flere. Compileren er intelligent nok til at finde ud af, hvordan den skal oprette den korrekte samlingstype baseret på den variabel, udtrykket tildeles.

Her er et par eksempler mere:

// Initialisering af en List<int> List<int> primtal = [2, 3, 5, 7, 11, 13]; // Initialisering af en Span<char> Span<char> vokalerSpan = ['a', 'e', 'i', 'o', 'u'];

Introduktion til Spread-Elementet (`..`)

Nu hvor vi forstår samlingsudtryk, kan vi introducere stjernen i showet: spread-elementet, repræsenteret ved to punktummer (`..`). Dette element kan kun bruges inde i et samlingsudtryk, og dets formål er at "sprede" eller "pakke ud" elementerne fra en eksisterende samling ind i den nye samling, der bliver oprettet.

Dette er utroligt nyttigt til at kombinere eller flette flere samlinger. Forestil dig, at du har to lister, og du vil oprette en tredje, der indeholder alle elementer fra begge. Før C# 12 ville du måske bruge LINQ's `Concat`-metode efterfulgt af `ToList()` eller `ToArray()`.

var forsteListe = new List<int> { 1, 2, 3 }; var andenListe = new List<int> { 4, 5, 6 }; // Den gamle måde var flettetListeGammel = forsteListe.Concat(andenListe).ToList();

Med spread-elementet bliver denne operation triviel og meget mere læsbar:

List<int> forsteListe = [1, 2, 3]; List<int> andenListe = [4, 5, 6]; // Den nye, elegante måde List<int> flettetListeNy = [..forsteListe, ..andenListe]; // Resultat: [1, 2, 3, 4, 5, 6]

Som du kan se, tager `..forsteListe` alle elementerne fra `forsteListe` og indsætter dem på den position. Det samme sker for `..andenListe`. Du kan også kombinere spread-elementer med individuelle elementer for fuld fleksibilitet:

int[] tal = [1, 2, 3]; int[] flereTal = [0, ..tal, 4, 5]; // Resultat: [0, 1, 2, 3, 4, 5]

Praktiske Anvendelser

Kloning af Samlinger

En almindelig opgave er at oprette en kopi af en samling. Spread-elementet gør dette utroligt enkelt. Det er dog vigtigt at huske, at dette skaber en shallow copy (overfladisk kopi).

int[] originalArray = { 10, 20, 30 }; int[] klonetArray = [..originalArray]; // klonetArray er nu et nyt array med værdierne [10, 20, 30] // Ændringer i klonetArray påvirker ikke originalArray klonetArray[0] = 99; // originalArray[0] er stadig 10

En shallow copy betyder, at selve samlingen er en ny instans, men hvis elementerne i samlingen er referencetyper (f.eks. objekter), vil begge samlinger pege på de samme objekter. Ændringer i selve objekterne vil derfor være synlige fra begge samlinger.

How to enumerate a variable in a spread element?
The variable in a spread element must be enumerable using a foreach statement. As shown in the previous example, you can combine spread elements with individual elements in a collection expression. A collection expression can be converted to different collection types, including:

Dynamisk Opbygning af Samlinger

Spread-elementet er perfekt til at opbygge samlinger betinget, uden at skulle bruge `if`-sætninger og `Add`-kald.

bool inkluderEkstraTal = true; List<int> basisTal = [1, 5, 10]; List<int> ekstraTal = [100, 200]; List<int> endeligListe = [ ..basisTal, ..(inkluderEkstraTal ? ekstraTal: Enumerable.Empty<int>()), 1000 ]; // Hvis inkluderEkstraTal er true, bliver resultatet: [1, 5, 10, 100, 200, 1000]

Sammenligning: Før og Nu

For at illustrere forbedringen i kodekvalitet og læsbarhed, er her en tabel, der sammenligner almindelige operationer før og efter C# 12.

OpgaveTraditionel C# (Før C# 12)Med Samlingsudtryk (C# 12)
Fletning af to arrays
var arr1 = new[] { 1, 2 }; var arr2 = new[] { 3, 4 }; var flettet = arr1.Concat(arr2).ToArray();
int[] arr1 = [1, 2]; int[] arr2 = [3, 4]; int[] flettet = [..arr1, ..arr2];
Opret en liste med start- og slutværdier
var midte = new List<string> { "B", "C" }; var komplet = new List<string> { "A" }; komplet.AddRange(midte); komplet.Add("D");
List<string> midte = ["B", "C"]; List<string> komplet = ["A", ..midte, "D"];
Shallow copy af en liste
var original = new List<int> { 5, 10, 15 }; var kopi = new List<int>(original);
List<int> original = [5, 10, 15]; List<int> kopi = [..original];

Hvilke Typer Understøttes?

En af de stærke sider ved samlingsudtryk er, hvor bredt de kan anvendes. Et samlingsudtryk kan konverteres til mange forskellige samlingstyper. Compileren undersøger måltypen og finder den mest effektive måde at oprette den på. Her er nogle af de understøttede typer:

  • Arrays (f.eks. int[], string[])
  • System.Span<T> og System.ReadOnlySpan<T>
  • Enhver type med en Create(ReadOnlySpan<T>) metode (mere om dette nedenfor)
  • Typer, der understøtter en samlings-initialisator, såsom System.Collections.Generic.List<T>. Dette kræver typisk, at typen implementerer IEnumerable<T> og har en tilgængelig Add-metode.
  • En række standard interfaces, herunder:
    • IEnumerable<T>
    • IReadOnlyCollection<T>
    • IReadOnlyList<T>
    • ICollection<T>
    • IList<T>

Denne brede understøttelse betyder, at du kan bruge den samme enkle syntaks på tværs af dit projekt, uanset om du arbejder med højtydende `Span`-baseret kode eller almindelige lister og arrays.

Avanceret Brug: Få Dine Egne Typer til at Understøtte Samlingsudtryk

Hvad nu hvis du har din egen brugerdefinerede samlingstype og gerne vil kunne initialisere den med den nye `[]`-syntaks? Det kan du opnå ved hjælp af en `CollectionBuilder`. Dette er en mere avanceret funktion, der giver dig fuld kontrol.

Processen består af to trin:

  1. Opret en builder-klasse: Denne klasse skal indeholde en statisk metode, typisk navngivet `Create`, som tager en `ReadOnlySpan<T>` som parameter og returnerer en instans af din samlingstype.
  2. Annoter din samlingstype: Brug `System.Runtime.CompilerServices.CollectionBuilderAttribute` på din samlingstype for at fortælle compileren, hvilken builder-klasse og -metode den skal bruge.

Lad os forestille os, at vi har en simpel type `FixedSizedList`:

// Vores brugerdefinerede samlingstype [CollectionBuilder(typeof(FixedSizedListBuilder), "Create")] public class FixedSizedList<T>: IEnumerable<T> { private readonly T[] _items; public FixedSizedList(ReadOnlySpan<T> items) { _items = items.ToArray(); } // Implementering af IEnumerable<T> ... public IEnumerator<T> GetEnumerator() => ((IEnumerable<T>)_items).GetEnumerator(); System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _items.GetEnumerator(); } // Vores builder-klasse internal static class FixedSizedListBuilder { internal static FixedSizedList<T> Create<T>(ReadOnlySpan<T> values) { return new FixedSizedList<T>(values); } }

Med disse to stykker kode på plads kan vi nu initialisere vores `FixedSizedList` med den nye, enkle syntaks:

FixedSizedList<int> minListe = [10, 20, 30, 40];

Dette gør dine egne API'er og datastrukturer meget mere brugervenlige og konsistente med resten af .NET-økosystemet.

Ofte Stillede Spørgsmål (FAQ)

Er spread-elementet (`..`) det samme som spread-operatoren i JavaScript?
Det er stærkt inspireret af JavaScripts spread-syntaks (`...`) og tjener et meget lignende formål for arrays og andre sekvenser. Den primære forskel er syntaksen (`..` i C# mod `...` i JS) og konteksten. I C# er spread-elementet en del af de mere generelle samlingsudtryk, som er stærkt typebestemte og compiler-optimerede.
Hvilken version af .NET og C# skal jeg bruge for at anvende dette?
Du skal bruge C# 12, som følger med .NET 8 eller en nyere version.
Hvordan påvirker dette ydeevnen?
Compileren er designet til at generere den mest effektive kode muligt. Når du for eksempel tildeler et samlingsudtryk til en `Span`, kan dataene allokeres direkte på stakken, hvilket er ekstremt hurtigt. For andre typer, som `List`, vil compileren forsøge at forud-allokere den korrekte kapacitet for at undgå unødvendige re-allokeringer. I de fleste tilfælde vil ydeevnen være lige så god eller bedre end den manuelle kode, den erstatter.
Kan jeg bruge `var` med samlingsudtryk?
Nej, du kan generelt ikke bruge `var` til at initialisere en variabel med et samlingsudtryk, fordi compileren har brug for at kende måltypen for at vide, hvilken type samling der skal oprettes. Du skal specificere typen eksplicit, f.eks. `List tal = [1, 2, 3];`.

Konklusion

Samlingsudtryk og spread-elementet i C# 12 repræsenterer et markant skridt fremad for sproget. De tilbyder en mere udtryksfuld, læsbar og effektiv måde at arbejde med samlinger på. Ved at fjerne meget af den standardkode (boilerplate), der tidligere var nødvendig for simple operationer som fletning og initialisering, frigør de udviklere til at fokusere på den faktiske forretningslogik. Uanset om du er en erfaren C#-udvikler eller ny til sproget, er dette en funktion, der er værd at tage i brug i dine .NET 8-projekter for at skrive renere og mere moderne kode.

Hvis du vil læse andre artikler, der ligner Ny Spread-operator i C# 12: En Dybdegående Guide, kan du besøge kategorien Sundhed.

Go up