Nieuw in C# 15

C# 15 bevat de volgende nieuwe functies. Probeer deze functies met behulp van de nieuwste versie van Visual Studio 2026 insiders of de .NET 11 preview-SDK:

C# 15 is de nieuwste C#-preview-versie. .NET 11 preview-versies ondersteunen C# 15. Zie C#-taalversiebeheer voor meer informatie.

U kunt de nieuwste .NET 11 preview-SDK downloaden via de .NET-downloadpagina. U kunt ook Visual Studio 2026-insiders downloaden, waaronder de .NET 11 preview-SDK.

Op de pagina 'Wat is er nieuw in C#' worden nieuwe functies toegevoegd wanneer deze beschikbaar zijn in openbare preview-versies. In de werksetsectie van de Roslyn-functiestatuspagina wordt bijgehouden wanneer toekomstige functies worden samengevoegd in de hoofdbranch.

U vindt alle belangrijke wijzigingen die zijn geïntroduceerd in C# 15 in ons artikel over belangrijke wijzigingen.

Opmerking

We zijn geïnteresseerd in uw feedback over deze functies. Als u problemen ondervindt met een van deze nieuwe functies, maakt u een nieuw probleem in de dotnet-/roslyn-opslagplaats .

Argumenten voor verzamelingsexpressie

U kunt argumenten doorgeven aan de constructor of factory-methode van de onderliggende verzameling met behulp van een with(...) element als het eerste element in een verzamelingsexpressie. Met deze functie kunt u capaciteit, vergelijkingsparameters of andere constructorparameters rechtstreeks in de syntaxis van de verzamelingsexpressie opgeven.

In het volgende voorbeeld ziet u hoe u een capaciteitsargument doorgeeft aan een List<T> constructor en een vergelijkingsfunctie met een HashSet<T>:

string[] values = ["one", "two", "three"];

// Pass capacity argument to List<T> constructor
List<string> names = [with(capacity: values.Length * 2), .. values];

// Pass comparer argument to HashSet<T> constructor
HashSet<string> set = [with(StringComparer.OrdinalIgnoreCase), "Hello", "HELLO", "hello"];
// set contains only one element because all strings are equal with OrdinalIgnoreCase

Zie het taalreferentieartikel over verzamelingsexpressies of de functiespecificatie voor meer informatie over argumenten voor verzamelingsexpressies. Zie Initializers voor objecten en verzamelingen voor informatie over het gebruik van argumenten voor verzamelingexpressies in initialisatiefuncties voor verzamelingen.

Uniontypen

C# 15 introduceert samenvoegtypen, die een waarde vertegenwoordigen die een van de verschillende casetypen kan zijn. Declareer een samenvoeging met het union trefwoord:

public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);

public union Pet(Cat, Dog, Bird);

Unions bieden impliciete conversies van elk casetype, en de compiler zorgt ervoor dat switch expressies uitputtend zijn over alle casetypen.

Pet pet = new Dog("Rex");

string name = pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
    Bird b => b.Name,
};

De runtime bevat de typen UnionAttribute en IUnion vanaf .NET 11 preview 5. Sommige functies uit de voorstelspecificatie zijn nog niet geïmplementeerd. Deze functies zijn beschikbaar in toekomstige previews.

Zie Union-typen in de taalreferentie of de functiespecificatie voor meer informatie.

Gesloten hiërarchieën

Vanaf C# 15 kunt u de closed wijzigingsfunctie toepassen op een klasse om een gesloten hiërarchie te declareren. Van een gesloten klasse kan alleen worden overgeërfd binnen de assembly waarin deze is gedeclareerd, waarmee de verzameling directe subklassen tijdens het compileren wordt vastgelegd:

public closed record class GateState;
public record class Closed : GateState;
public record class Open(float Percent) : GateState;

Omdat de compiler elke directe afstammeling kent, is een switch expressie die elke afhandelt volledig en heeft geen standaardarm nodig:

string Describe(GateState state) => state switch
{
    Closed => "closed",
    Open(var percent) => $"{percent}% open",
    // No warning: every direct descendant of 'GateState' is handled.
};

De closed wijziging is een contextueel trefwoord. Een closed klasse is impliciet abstract en kan niet worden gecombineerd met sealed, staticof een expliciete abstract wijziging. De afleiding is niet transitief: een niet-gesloten afstammeling van een gesloten klasse kan nog steeds worden afgeleid uit andere assembly's. Als u de volledige controle van de hiërarchie wilt uitbreiden, markeert u ook tussenliggende nakomelingen closed .

Zie de gesloten wijzigings- en gesloten hiërarchiepatronen in de taalverwijzing of de functiespecificatie voor meer informatie.

Indexeerders voor uitbreidingen

Vanaf C# 15 kunt u indexeerfuncties in een extension blok declareren. Met extensieindexeerfuncties kunt u indexeren in een ontvanger alsof de indexeerfunctie is gedeclareerd voor het ontvangertype. Omdat indexeerfuncties altijd exemplaarleden zijn, moet een extensieblok dat een indexeerfunctie declareert een benoemde ontvangerparameter opgeven.

In het volgende voorbeeld wordt in IEnumerable<int> een indexer met alleen een getter gedeclareerd die het element op een opgegeven positie retourneert:

public static class SequenceIndexer
{
    extension(IEnumerable<int> sequence)
    {
        public int this[int index] => sequence.ElementAt(index);
    }
}

U indexeer in de ontvanger alsof de indexeerfunctie lid was van het ontvangertype:

IEnumerable<int> numbers = Enumerable.Range(1, 10);
int third = numbers[2];

Zie Extensiedeclaratie in de taalverwijzing of de functiespecificatie voor meer informatie.

Gelabeld break en continue

Vanaf C# 15 kunnen break- en continue-instructies een label op een omvattende constructie opgeven. Gebruik een gelabelde break om een omsluitende lus of switch-instructie te verlaten. Gebruik een label continue om de volgende iteratie van een omsluitlus te starten.

Gelabelde break en continue vervangen de kunstgrepen die u anders zou gebruiken om de controlestroom door geneste lussen te sturen, zoals een Booleaanse vlag die u in een binnenste lus instelt en vervolgens op elk buitenliggend niveau controleert, of een goto die voorbij de lussen springt. Door de doellus direct in de spronginstructie te benoemen, vervalt die extra administratie en wordt de bedoelde controlestroom gemakkelijker leesbaar.

outer: for (int row = 0; row < grid.Height; row++)
{
    for (int column = 0; column < grid.Width; column++)
    {
        if (grid[row, column].IsBlocked)
        {
            continue outer;
        }

        if (grid[row, column].IsGoal)
        {
            break outer;
        }
    }
}

Plaats het label rechtstreeks op de lus of switch instructie die het identificeert. Zonder label behouden break en continue hun oorspronkelijke gedrag en verwijzen ze naar de binnenste toepasselijke instructie.

De IDE0410-stijlregel markeert de patronen met een Booleaanse vlag en goto, die door een gelabelde spronginstructie kunnen worden vervangen, en toont voor-en-na-voorbeelden van beide.

Zie Jump-instructies in de taalreferentie of de functiespecificatie voor meer informatie.

Geheugenveiligheid

C# 15 begint met een multirelease-inspanning om de geheugenveiligheid in de taal opnieuw te definiëren. Het doel is om de unsafe context te koppelen aan de bewerkingen die daadwerkelijk toegang hebben tot onbeheerd geheugen, in plaats van het bestaan van aanwijzertypen. De meeste beveiligingsproblemen in het geheugen zijn afkomstig van deze toegangsbewerkingen, dus de taal maakt ze opvallen voor revisoren en auditors.

In het complete model markeert unsafe op een lid dit als requires-unsafe: de auditverplichting verschuift naar de aanroeper, die het lid vanuit een unsafe-context moet gebruiken. Een assembly kiest voor deze afdwinging en de compiler registreert de keuze met het System.Runtime.CompilerServices.MemorySafetyRulesAttribute kenmerk. Het model voegt ook een safe contextueel trefwoord toe dat extern leden en velden met expliciete indeling als veilig markeert. Samen maken deze regels de grenzen van potentiële geheugenonveiligheid expliciet voor een programma.

De eerste stap omvat de pointerversoepelingen. Wanneer u compileert met de preview taalversie, hebben de volgende bewerkingen geen context meer nodig unsafe :

  • Het declareren van een aanwijzertype en het opvragen van het adres van een variabele met de operator &.
  • De fixed instructie, waarmee een variabele wordt vastgemaakt.
  • stackalloc Een expressie converteren naar een aanwijzer.
  • De sizeof-operator toegepast op elk onbeheerd type.

In het volgende voorbeeld wordt een aanwijzer zonder unsafe context gemaakt en vastgemaakt:

int number = 42;
int* pointer = &number;

int[] numbers = [10, 20, 30];
fixed (int* first = numbers)
{
    // Dereferencing the pointer still requires an unsafe context.
}

De bewerkingen die toegang bieden tot het geheugen waarnaar wordt verwezen, zoals pointerindirectie (*p), toegang tot pointerleden (p->member), toegang tot pointerelementen (p[i]) en het aanroepen van functiepointers, vereisen nog steeds een unsafe-context.

C# 15 voegt ook een unsafe expressie toe, unsafe(expression)waarmee een onveilige context voor één expressie wordt vastgesteld. Het is handig wanneer een unsafe blok niet syntactisch kan worden weergegeven, zoals een veld-initialisatiefunctie, een constructor-initialisatiefunctie of een catch filter:

class Header
{
    // A field initializer can't contain an unsafe block, but it can contain an unsafe expression.
    static readonly int Signature = unsafe(ReadSignature());

    static unsafe int ReadSignature()
    {
        int rawValue = 0x1234;
        int* pointer = &rawValue;
        return *pointer;
    }
}

Net als de rest van de preview van geheugensveiligheid vereisen expressies met unsafe de taalversie preview en de compileroptie AllowUnsafeBlocks.

De compiler herkent het safe contextuele trefwoord ook als een wijzigingsfunctie voor extern leden en velden met expliciete indeling. Het ledenmodel requires-unsafe en de opt-in van de assembly voor de bijgewerkte regels voor geheugenveiligheid zijn echter nog niet beschikbaar, dus safe en unsafe hebben momenteel geen effect op aanroepers.

Zie Onveilige code, aanwijzertypen en functiewijzers in de taalreferentie of de functiespecificatie voor meer informatie.

Zie ook