CodeGym /Kurse /C# SELF /Neue Möglichkeiten Span<...

Neue Möglichkeiten Span<T>

C# SELF
Level 65 , Lektion 3
Verfügbar

1. Einführung

Stell dir vor, du hast ein großes Datenarray, zum Beispiel aus einer Datei gelesene Bytes. Du möchtest einen Teil davon verarbeiten — einen Abschnitt als String lesen oder einfach einen Datenbereich an eine Methode übergeben. Im "alten" Stil hättest du diese Bytes in ein neues Array kopiert — das ist langsam und verbraucht Speicher. Und wenn es Dutzende oder Hunderte solcher Abschnitte gibt?

Hier kommt der Held ins Spiel — Span<T>. Das ist eine spezielle Struktur, die einen Slice (view) über ein Array (oder jeden zusammenhängenden Speicherblock) beschreibt, ohne die Daten zu kopieren, sondern einfach auf den entsprechenden Bereich zeigt. Wichtig: Span<T> ist keine neue Collection, sondern ein sicheres "Fenster" in ein bestehendes Array!

Hauptmerkmale und Einschränkungen von Span<T>

  • Span<T> ist eine Struktur (value type), die ein "Fenster" auf Speicher darstellt und Elemente nicht kopiert.
  • Zeigt nur auf zusammenhängenden Speicher: Array, Teil eines Arrays, stackalloc-Block, String-Speicher über spezielle Methoden oder Speicher in unsafe-Code.
  • Span<T> lebt auf dem Stack. Du kannst ihn nicht in einem Klassenfeld speichern oder direkt aus async-Methoden zurückgeben.
  • Garantiert Type-Safety: Zugriff auf Speicher ohne manuelles Pointer-Handling (sofern du nicht unsafe verwendest).

Kurz zu den neuen Möglichkeiten in C# 14

C# 14 erweitert die Arbeit mit Slices: bequeme Ranges .. und Endindizes ^1, verbesserte Pattern-Matching-Patterns für Arrays und Slices und weiteren "syntaktischen Zucker". Dazu kommen wir am Ende zurück.

2. Wie erstellt man Span<T>?

Beginnen wir mit einem einfachen Array und erstellen einen Span:

int[] numbers = { 10, 20, 30, 40, 50, 60 };
Span<int> midSpan = new Span<int>(numbers, 2, 3); // ab dem 3. Element (Index 2) 3 Elemente nehmen

Jetzt zeigt midSpan auf die Elemente 30, 40, 50 — das ist keine Kopie, sondern ein Blick auf die "lebenden" Array-Elemente. Wenn du sie über den Span änderst, änderst du das Originalarray.

midSpan[0] = 100;
Console.WriteLine(numbers[2]); // Gibt 100 aus

Warum nicht direkt Array-Slices verwenden?

Klassisches LINQ-Slicing erzeugt ein neues Array:

int[] slice = numbers.Skip(2).Take(3).ToArray(); // <-- hier wird eine Kopie erstellt!

Das ist zeit- und speicherintensiv. Span löst das Problem überflüssiger Kopien — und funktioniert nicht nur mit Zahlen.

3. Noch mehr Wege, Span<T> zu erstellen

Existierendes Array

Span<int> mySpan = numbers; // Implizite Konvertierung vom Array zu Span

Teil eines Arrays

int[] numbers = {10, 20, 30, 40, 50, 60};
Span<int> part = numbers.AsSpan(1, 4); // 4 Elemente ab Index 1: {20, 30, 40, 50}

Stack-Speicher (stackalloc)

Span<T> erlaubt das Allozieren von Arrays auf dem Stack:

Span<byte> buffer = stackalloc byte[128];
for (int i = 0; i < buffer.Length; i++)
    buffer[i] = (byte)i;

Stack-Speicher ist schnell und wird automatisch beim Verlassen der Methode freigegeben. Die Größe sollte jedoch vernünftig sein — Megabytes auf dem Stack sind nicht akzeptabel.

Strings und ReadOnlySpan<char>

Strings in .NET sind immutable, daher verwenden wir ReadOnlySpan<char>:

string greeting = "Hello, C# world!";
ReadOnlySpan<char> span = greeting.AsSpan(7, 8);   // "C# world"
Console.WriteLine(span.ToString());                // C# world

4. Lass uns ein Beispiel zusammenbauen!

using System;

class Program
{
    static void Main()
    {
        int[] orderTotals = { 100, 200, 300, 400, 500, 600, 700 };
        Console.WriteLine("Die gesamte Bestellhistorie: ");
        foreach (int total in orderTotals)
            Console.Write(total + " ");
        Console.WriteLine();
        
        Console.WriteLine("Bestellungen von 2 bis 4 anzeigen (Indizes 1-3):");
        Span<int> recentOrders = orderTotals.AsSpan(1, 3);
        foreach (int t in recentOrders)
            Console.Write(t + " ");
        Console.WriteLine();
        
        // Daten über Span mutieren
        recentOrders[1] = 999;
        Console.WriteLine("Nach der Änderung über Span:");
        foreach (int total in orderTotals)
            Console.Write(total + " ");
        Console.WriteLine();
    }
}

Der Slice recentOrders arbeitet tatsächlich direkt mit dem Array — es ist keine Kopie.

5. Sicherheit, Performance und Checks

  • Sparsamer Umgang mit Speicher: keine unnötigen Kopien.
  • Bounds-Checks schützen vor Zugriffen außerhalb des Arrays.
  • JIT-Optimierungen sorgen für sehr schnellen Zugriff (ohne unsafe).

Interaktion mit Methoden

Methoden können Span<T> oder ReadOnlySpan<T> akzeptieren. Wenn du die Daten nicht verändern willst — benutze ReadOnlySpan<T>.

static int Sum(Span<int> slice)
{
    int sum = 0;
    foreach (var item in slice)
        sum += item;
    return sum;
}

int[] data = { 1, 2, 3, 4, 5, 6, 7 };
Console.WriteLine(Sum(data.AsSpan(2, 3))); // 3+4+5=12

6. Verwendung von Ranges (range)

Seit C# 8 gibt es Ranges .. und Endindizes ^ — sie funktionieren hervorragend mit Slices.

int[] arr = { 10, 20, 30, 40, 50, 60 };

Span<int> span = arr[2..5]; // Indizes 2,3,4 - also 30, 40, 50

Bei Ranges ist der Startindex inklusiv, der Endindex exklusiv.

Endindizes

int lastElement = arr[^1];     // letztes Element (60)
Span<int> lastTwo = arr[^2..]; // die letzten zwei Elemente (50, 60)

Ranges und Strings

string code = "SpanMagic!";
var mid = code[4..9]; // "Magic"

Dieser Slice ist ein ReadOnlySpan<char>; um einen String zu bekommen, rufe ToString() auf.

7. Moderne Patterns für die Arbeit mit Slices (C# 14)

Neue Patterns erleichtern das Zerlegen von Arrays und Slices.

if (arr is [10, 20, .. var rest]) // .. fängt den "Tail" des Arrays ein
{
    Console.WriteLine("Anfang passt, Tail:");
    foreach (var x in rest)
        Console.WriteLine(x);
}
if (arr is [.., 50, 60])
    Console.WriteLine("Array endet mit 50, 60");

Vergleich: Array, ArraySegment und Span

Typ Mutierbar Kopiert Daten? Auf dem Stack möglich Klasse/Struktur Felder in Klassen möglich
T[]
ja - nein Klasse ja
ArraySegment<T>
ja nein nein Struktur ja
Span<T>
ja nein ja Struktur nein
ReadOnlySpan<T>
nein nein ja Struktur nein

Mit anderen Worten: Span<T> ist die Evolution von ArraySegment<T>: performanter und sicherer.

8. Typische Fehler und Fallstricke

Der Versuch, ein Span<T> in einem Klassenfeld zu speichern. Das geht nicht: Felder leben auf dem Heap, aber Span<T> muss auf dem Stack leben. Verwende ArraySegment<T> oder Indizes zur Adressierung.

Zurückgeben/Speichern eines Span<T> aus async-Methoden. Das ist nicht erlaubt: Asynchronität bricht den Stack. Gib die Daten anders weiter — zum Beispiel als Array, Memory<T>/ReadOnlyMemory<T>.

Falscher Range bei der Erstellung eines Slices. Das Überschreiten der Grenzen führt zur Laufzeit-Ausnahme. Prüfe stets Länge und Grenzen bevor du einen Slice bildest.

Vergessen, dass der Span das Original widerspiegelt. Jede Änderung über Span<T> ändert das Ausgangsarray. Wenn du eine unabhängige Kopie brauchst — kopiere die Daten explizit.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION