CodeGym /Kurse /C# SELF /Memory<T> und ...

Memory<T> und ReadOnlyMemory<T>

C# SELF
Level 65 , Lektion 4
Verfügbar

1. Einführung

Alles begann damit, dass die .NET-Entwickler die Arbeit mit großen Datenmengen beschleunigen und den Entwicklern die Möglichkeit geben wollten, das möglichst bequem und sicher zu tun. Zuerst kam Span<T>, das ein "Fenster" in einen zusammenhängenden Speicherbereich ist und einen Teil eines Arrays, eines Strings oder sogar Speicher außerhalb von .NET (z. B. via P/Invoke) zeigen kann.

Allerdings hat Span<T> eine starke Einschränkung: es darf immer nur auf dem Stack leben. Man kann es nicht in Feldern von Klassen speichern, nicht aus Methoden zurückgeben und nicht zwischen asynchronen Methoden übertragen. Der Grund ist Sicherheit: wenn jemand eine Referenz auf Speicher behält, der nicht mehr existiert, stürzt die Anwendung garantiert ab.

Manchmal muss man Slices von Daten aus Methoden zurückgeben, sie in Collections oder Klassenfeldern speichern oder in asynchronen APIs verwenden. Hier kommt Memory<T> ins Spiel — im Grunde ist das die sichere, "langlebige" Version von Span<T>, die auf dem Heap leben, zwischen Threads übergeben, in Properties und Objekten gehalten werden kann und sich wie ein normales .NET-Objekt verhält.

Es gibt auch einen "älteren Bruder" — ReadOnlyMemory<T>, der, wie man sich denken kann, keine Änderungen an den zugrundeliegenden Daten erlaubt, dafür aber überall lesend verwendet werden kann.

2. Worin besteht der Unterschied zwischen Span<T> und Memory<T>

Hier eine kleine Tabelle zum Vergleich:

Span<T>
Memory<T>
Wo es lebt Nur auf dem Stack (stack only) Auf Stack und Heap (heap/stack)
Kann in einem Feld gespeichert werden ❌ Nein ✅ Ja
Kann aus einer Methode zurückgegeben werden ❌ Nein ✅ Ja
Asynchrone/await-Methoden ❌ Nicht möglich ✅ Möglich
Veränderbar ✅ Es gibt auch ReadOnlySpan<T> ✅ Es gibt auch ReadOnlyMemory<T>
Erlaubt Slicing von Daten ✅ Ja ✅ Ja

Wenn du schnell über Daten innerhalb einer Methode iterieren willst — nimm Span<T>. Wenn du das Ergebnis nach außen zurückgeben oder in einem Klassenfeld speichern musst — benutze Memory<T>. Und wenn die Daten nur gelesen werden sollen — nimm ReadOnlyMemory<T>.

3. Signatur und Grundaufbau von Memory<T>

Wie üblich: Memory<T> ist generic, also ein universeller Typ. Du kannst Memory<int>, Memory<byte>, Memory<char> oder sogar Memory<MyType> erstellen. Innerhalb von Memory<T> steckt eine Referenz auf ein Array, einen String oder eine andere Datenquelle sowie eine Angabe des Bereichs (von welchem Index und wie viele Elemente).

Um aus Memory<T> schnellen Zugriff für Verarbeitung zu bekommen, verwendet man seine Eigenschaft Span — damit erhältst du sofort ein Span<T>, das innerhalb einer synchronen Methode genutzt werden kann.

4. Wie man Memory<T> erstellt: Praxis

Beispiel 1. Erstellung aus einem Array

int[] numbers = { 1, 2, 3, 4, 5, 6 };
Memory<int> memory = new Memory<int>(numbers); // Ganzes Array

// Man kann einen "Slice" nehmen — einen Teil des Arrays
Memory<int> slice = memory.Slice(2, 3); // Elemente 2, 3 und 4

Beispiel 2. Erstellung aus einem String (über Memory<char>)

string text = "Hello, world!";
Memory<char> charMemory = text.AsMemory(); // Ganzer Text als Memory
Memory<char> subMemory = charMemory.Slice(7, 3); // ab dem 7. Zeichen, 3 Zeichen ("world")

Beispiel 3. Nutzung von ReadOnlyMemory<T>

Genauso, nur gegen Änderungen geschützt:

int[] data = { 10, 20, 30, 40 };
ReadOnlyMemory<int> readOnly = data; // Verhindert Änderungen über dieses Objekt

5. Umwandlung zwischen Memory<T> und Span<T>

Mit Memory<T> so flexibel und schnell wie mit Span<T> zu arbeiten, geht nicht direkt — es ist ja für andere Szenarien gedacht. Wenn du jedoch wirklich schnell ein Stück Speicher verarbeiten musst, kannst du ein "momentanes" Span<T> aus dem Memory über die Eigenschaft Span bekommen:

void ProcessData(Memory<int> memory)
{
    Span<int> span = memory.Span;
    for (int i = 0; i < span.Length; i++)
    {
        span[i] += 100;
    }
}

Beachte: Span<T> funktioniert nur innerhalb der Methode. Wenn du versuchst, es nach außen zurückzugeben, gibt der Compiler einen Fehler.

Bei ReadOnlyMemory<T> ist es analog, nur bekommst du ein ReadOnlySpan<T>, das Änderungen verhindert:

void PrintData(ReadOnlyMemory<int> memory)
{
    ReadOnlySpan<int> roSpan = memory.Span;
    foreach (var item in roSpan)
        Console.WriteLine(item);
}

6. Einsatz in realen Aufgaben

Asynchrone Datenverarbeitung

Hier spielt Memory<T> seine Stärken aus. Es kann in asynchronen Methoden verwendet werden! Zum Beispiel asynchrones Lesen von Dateien:

using System.IO;
using System.Threading.Tasks;

public async Task ReadFileAsync(string path)
{
    byte[] buffer = new byte[4096];
    using var stream = File.OpenRead(path);
    int bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length));
    // Jetzt kannst du mit buffer arbeiten
}

Hier übergibt AsMemory den Buffer direkt an die asynchrone Methode, und es gibt keine Probleme mit Scope oder zerstörtem Speicher, wie es bei Span<T> der Fall wäre.

Speichern von Daten-Slices in Properties und Feldern

Manchmal will man eine Klasse, die ein "Stück" eines großen Arrays für spätere Verwendung hält:

class DataChunk
{
    public Memory<byte> Data { get; }

    public DataChunk(Memory<byte> data)
    {
        Data = data;
    }
}

7. Nutzung mit Collections, Strings und Arrays

Mit Arrays

Meist so:

byte[] bytes = { 1, 2, 3, 4, 5 };
Memory<byte> mem = bytes;          // Ganzes Array
Memory<byte> part = mem.Slice(2);  // Ab dem dritten Element bis zum Ende

Mit Strings

Über AsMemory():

string hello = "Hello, Memory!";
ReadOnlyMemory<char> mem = hello.AsMemory(6, 6); // "Memory"

Mit Collections (z. B. List<T>)

Man kann nicht direkt Memory<T> aus einer List<T> erstellen. Nur über ein Array:

List<int> list = new List<int> { 1, 2, 3 };
Memory<int> mem = list.ToArray(); // Kopie, keine Referenz!

Sei vorsichtig: wenn du Kopien vermeiden willst — halte die Daten im Array.

8. Typische Fehler bei der Arbeit mit Memory<T>

Fehler Nr. 1: Versuch, Span<T> statt Memory<T> in einem Klassenfeld zu verwenden. Man darf Span<T> nicht in Klassenfeldern speichern, weil es an den Stack gebunden ist. Der Compiler wird einen Fehler melden. Verwende Memory<T>, um im Heap zu speichern.

Fehler Nr. 2: Erwartung, dass beim Slicing Daten kopiert werden. Memory<T> kopiert die Daten nicht, es erzeugt ein "Fenster" in das vorhandene Array. Wenn du Daten über ein Memory<T> änderst, wirkt sich das auf alle anderen aus, die auf denselben Speicher verweisen.

Fehler Nr. 3: Versuch, Memory<T> direkt aus List<T> zu erstellen. Memory<T> arbeitet nur mit Arrays, da List<T> die Lage der Daten im Speicher verändern kann. Wandle die Liste mit ToArray() in ein Array um.

Fehler Nr. 4: Ignorieren von ReadOnlyMemory<T> für unveränderliche Daten. Wenn die Daten nicht verändert werden müssen, verwende ReadOnlyMemory<T> statt Memory<T> für mehr Sicherheit.

1
Umfrage/Quiz
Speicher in C#, Level 65, Lektion 4
Nicht verfügbar
Speicher in C#
Speicheraufbau in .NET
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION