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.

2
Aufgabe
C# SELF, Level 65, Lektion 3
Gesperrt
Verwendung von Bereichen und Indexierung vom Ende, um einen Teil eines Arrays anzuzeigen
Verwendung von Bereichen und Indexierung vom Ende, um einen Teil eines Arrays anzuzeigen
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION