Start Debugging
2026-10-15 Aktualisiert 2026-11-02 csharpcsharp-14dotnet-11ref-locals Edit on GitHub

Was ist ref-safe-context in C#, und wie bestimmt der Compiler ihn für ref-Lokalvariablen?

C#-Escape-Regeln für Referenzen verstehen: compilergeprüfte Beispiele zu ref-Lokalvariablen, Neuzuweisung, scoped und Heap- gegenüber Stack-Spans.

Diese Website nutzt Walter, einen KI-Agenten, für Recherche, Texte und Pflege. Redaktion und Überprüfung

Eine Referenz darf die Lebensdauer des Speichers, auf den sie zeigt, nicht überschreiten. C# prüft diesen Zusammenhang zur Kompilierzeit. Eine mit einer lokalen Methodenvariablen initialisierte ref-Lokalvariable lässt sich nicht allein deshalb zurückgeben, weil sie später auf ein Arrayelement umgebogen wird.

Korrektur — 2. November 2026: Für die frühere Behauptung eines Tests mit .NET 11 RC1 gab es keine erhaltenen Belege; sie wurde entfernt. Die hier beschriebenen positiven und negativen Beispiele wurden mit SDK 10.0.302, net10.0 und LangVersion 14 kompiliert. Die Fehlercodes beziehen sich auf genau diese Codeformen; verwandte Ausdrücke können andere Diagnosen auslösen.

Ref-safe-context und safe-context

ref-safe-context betrifft eine Referenz auf Speicher. Der verwandte safe-context betrifft einen Wert, der selbst Referenzen enthalten kann, etwa Span<T>. Die lokale Span-Variable und der durch den Span beschriebene Speicher sind verschiedene Dinge.

Die Sprachspezifikation beschreibt vier ineinander geschachtelte Kontexte:

KontextTypisches Beispiel
declaration-blockReferenz auf eine gewöhnliche lokale Variable
function-memberWertparameter oder scoped-Parameter
return-onlyGewöhnlicher ref-Parameter, der zurückgegeben werden darf
caller-contextArrayelement oder geeignetes Klassenfeld

Diese Bezeichnungen bedeuten nicht, dass jeder Speicher bis zur Methodenrückkehr lebt. Verschachtelte Blöcke und scoped-Beschränkungen bleiben relevant. Die Sprachspezifikation enthält die vollständigen Regeln einschließlich Parametern und Aufrufen.

Der Initialisierer begrenzt eine ref-Lokalvariable ohne scoped

Dieses negative Beispiel erzeugt im geprüften Build CS8157:

class Example
{
    static ref int CannotReturn(int[] array)
    {
        int local = 1;
        ref int reference = ref local;
        reference = ref array[0];
        return ref reference; // CS8157
    }
}

Die spätere Neuzuweisung ist erlaubt, macht aus der lokal abgeleiteten Referenz aber keine zurückgebbare Referenz. Eine gewöhnliche lokale Variable direkt zurückzugeben, führt dagegen zu CS8168:

class Example
{
    static ref int CannotReturn()
    {
        int local = 1;
        return ref local; // CS8168
    }
}

Neuzuweisung darf keine kürzer lebende Referenz übernehmen

Eine Arrayelement-Referenz zunächst zu speichern und anschließend eine lokale Referenz zuzuweisen, erzeugt CS8374:

class Example
{
    static void CannotNarrow(int[] array)
    {
        ref int reference = ref array[0];
        int local = 1;
        reference = ref local; // CS8374
    }
}

Ein ref-Parameter hat einen anderen Escape-Vertrag als ein Arrayelement. Initialisierung mit dem Array und anschließende Zuweisung des Parameters erzeugt CS9079:

class Example
{
    static ref int CannotAssign(int[] array, ref int parameter)
    {
        ref int reference = ref array[0];
        reference = ref parameter; // CS9079
        return ref reference;
    }
}

Die umgekehrte Reihenfolge kompilierte in derselben Prüfung erfolgreich:

class Example
{
    static ref int CanReturn(int[] array, ref int parameter)
    {
        ref int reference = ref parameter;
        reference = ref array[0];
        return ref reference;
    }
}

Wähle einen Initialisierer, dessen Vertrag zu jeder beabsichtigten Verwendung passt. Deklarationen zu verschieben oder scoped hinzuzufügen ist keine allgemeine Lösung für jeden Escape-Fehler.

Scoped-Referenzen bleiben lokal

Diese Änderung innerhalb desselben Blocks kompilierte erfolgreich:

class Example
{
    static void LocalUse(int[] array)
    {
        scoped ref int reference = ref array[0];
        int local = 1;
        reference = ref local;
        reference = 5;
    }
}

Die Rückgabe dieser scoped-Referenz wird abgelehnt. Eine zusätzliche Prüfung mit local in einem engeren, verschachtelten Block lehnte auch die Neuzuweisung mit CS8374 ab. scoped beseitigt weder Block-Lebensdauergrenzen noch erlaubt es die Rückgabe.

Ein scoped-Parameter kann außerdem ausdrücken, dass ein Argument nicht über das Methodenergebnis entweicht. Das ist ein Signaturvertrag; der Compiler muss dafür nicht die Implementierung eines beliebigen Helpers untersuchen. Der C#-11-Entwurf erklärt die Details und Fälle mit [UnscopedRef].

Ein Span liegt nicht zwangsläufig auf dem Stack

Beide Beispiele deklarieren einen lokalen Span, doch nur das erste Ergebnis darf entweichen:

using System;

class Example
{
    static ref int HeapBacked()
    {
        Span<int> values = new int[2];
        return ref values[0]; // Compiles in the verified probe
    }

    static ref int StackBacked()
    {
        Span<int> values = stackalloc int[2];
        return ref values[0]; // CS8352 in this exact probe
    }
}

Der arraybasierte Span beschreibt verwalteten Heap-Speicher. Eine Referenz darauf zurückzugeben ist etwas anderes als eine Referenz auf die lokale Span-Variable selbst zurückzugeben. Verwaltete Referenzen halten ihre referenzierten Objekte erreichbar; Heap-Objekte leben deshalb nicht unbegrenzt.

Ein praktischer Algorithmus mit ref-Rückgabe kann einen Span des Aufrufers verwenden und leere Eingaben behandeln:

using System;

static ref int MaxRef(Span<int> values)
{
    if (values.IsEmpty)
        throw new ArgumentException("At least one element is required", nameof(values));

    ref int best = ref values[0];
    for (int i = 1; i < values.Length; i++)
        if (values[i] > best) best = ref values[i];
    return ref best;
}

int[] data = { 3, 9, 4 };
MaxRef(data) = 0;
Console.WriteLine(string.Join(",", data)); // 3,0,4

Die Diagnose im Kontext lesen

Die geprüften Beispiele unterscheiden direkte Rückgabe lokaler Variablen (CS8168), nicht zurückgebbare ref-Lokalvariablen (CS8157), engere Referenzzuweisungen (CS8374) und einen return-only-Parameter, der einer weiter reichenden Referenz zugewiesen wird (CS9079). Der direkte Stack-Span-Ausdruck oben erzeugte CS8352. Ein weiterer Alias oder Helper-Aufruf kann die Diagnose ändern, ohne das Lebensdauerproblem zu ändern.

Async-/Iterator-Einschränkungen sind ein separates Thema: Eine Referenz darf nicht über einen Suspendierungspunkt erhalten bleiben, wenn die Sprache dies verbietet. Nicht jede Ablehnung lässt sich allein über den Initialisierer erklären.

Verwandt: Span und sein Speicher, List, Span und ReadOnlySpan und Deklarationen von Referenzvariablen.

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.