Co nowego w języku C# 15

Język C# 15 zawiera następujące nowe funkcje. Wypróbuj te funkcje, korzystając z najnowszej wersji testów programu Visual Studio 2026 lub zestawu SDK platformy .NET 11 w wersji zapoznawczej:

C# 15 to najnowsza wersja zapoznawcza języka C#. Wersje zapoznawcza platformy .NET 11 obsługują język C# 15. Aby uzyskać więcej informacji, zobacz wersjonowanie języka C#.

Najnowszy zestaw SDK platformy .NET 11 w wersji zapoznawczej można pobrać ze strony pobierania platformy .NET. Możesz również pobrać wersję insider programu Visual Studio 2026, która zawiera wersję zapoznawczą zestawu SDK .NET 11.

Strona "Co nowego w języku C#" dodaje nowe funkcje, gdy są one dostępne w publicznej wersji zapoznawczej. Sekcja zestawu roboczego na stronie stanu funkcji roslyn śledzi, kiedy nadchodzące funkcje są scalane z gałęzią główną.

Wszelkie zmiany powodujące niezgodność wprowadzone w języku C# 15 można znaleźć w naszym artykule dotyczącym zmian powodujących niezgodność.

Uwaga / Notatka

Interesuje nas Twoja opinia na temat tych funkcji. Jeśli znajdziesz problemy z dowolną z tych nowych funkcji, utwórz nowe zgłoszenie w repozytorium dotnet/roslyn.

Argumenty wyrażeń kolekcji

Argumenty można przekazać do konstruktora lub metody fabrycznej bazowej kolekcji przy użyciu with(...) elementu jako pierwszego elementu w wyrażeniu kolekcji. Ta funkcja umożliwia określanie pojemności, komparatorów lub innych parametrów konstruktora bezpośrednio w składni wyrażenia kolekcji.

W poniższym przykładzie pokazano, jak przekazać argument pojemności do konstruktora List<T> i komparator jako argument do 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

Aby dowiedzieć się więcej na temat argumentów wyrażeń kolekcji, zobacz artykuł referencyjny języka dotyczący wyrażeń kolekcji lub specyfikacji funkcji. Aby uzyskać informacje na temat używania argumentów wyrażeń kolekcji w inicjatorach kolekcji, zobacz Inicjatory obiektów i kolekcji.

Typy unii

Język C# 15 wprowadza typy związków, które reprezentują wartość mogącą być jednym z kilku typów wariantów. Zadeklaruj unię za pomocą słowa kluczowego union :

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

public union Pet(Cat, Dog, Bird);

Związki zapewniają niejawne konwersje z każdego typu przypadku, a kompilator zapewnia, że switch wyrażenia są wyczerpujące we wszystkich typach przypadków:

Pet pet = new Dog("Rex");

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

Środowisko uruchomieniowe zawiera typy UnionAttribute i IUnion począwszy od .NET 11 (wersja zapoznawcza 5). Niektóre funkcje specyfikacji propozycji nie zostały jeszcze zaimplementowane. Te funkcje będą dostępne w przyszłych wersji zapoznawczych.

Aby uzyskać więcej informacji, zobacz Typy unii w dokumentacji językowej lub specyfikacji funkcji.

Zamknięte hierarchie

Począwszy od języka C# 15, można zastosować closed modyfikator do klasy w celu zadeklarowania zamkniętej hierarchii. Zamknięta klasa może być dziedziczona wyłącznie w obrębie zestawu, w którym została zadeklarowana, co ustala zbiór jej bezpośrednich klas pochodnych w czasie kompilacji:

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

Ponieważ kompilator zna każdy bezpośredni podtyp, wyrażenie switch, które obsługuje każdy z nich, jest kompletne i nie wymaga gałęzi domyślnej:

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

Modyfikator closed jest kontekstowym słowem kluczowym. Klasa closed jest niejawnie abstract i nie może być łączona z sealed, staticani jawnym abstract modyfikatorem. Dziedziczenie nie jest tranzytywne: po niezamkniętej klasie pochodnej klasy zamkniętej nadal można dziedziczyć w innych zestawach. Aby rozszerzyć wyczerpującość sprawdzania hierarchii, oznacz również obiekty potomne closed pośrednie.

Uwaga / Notatka

W języku C# 15 (wersja zapoznawcza 5) środowisko uruchomieniowe nie jest jeszcze dostarczane System.Runtime.CompilerServices.ClosedAttribute. Dopóki tak się nie zrobi, każdy projekt używający closed modyfikatora musi zadeklarować sam atrybut:

namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class ClosedAttribute : Attribute { }

Aby uzyskać więcej informacji, zobacz zamknięte modyfikatory i zamknięte wzorce hierarchii w dokumentacji językowej lub specyfikacji funkcji. Możesz skopiować przykłady z tej sekcji, w tym obejście ClosedAttribute, z projektu fragmentów kodu keywords w repozytorium GitHub dotnet/docs.

Indeksatory rozszerzeń

Począwszy od języka C# 15, można zadeklarować indeksatory w extension bloku. Indeksatory rozszerzeń umożliwiają indeksowanie w odbiorniku tak, jakby indeksator został zadeklarowany w typie odbiorcy. Ponieważ indeksatory są zawsze składowymi instancji, blok rozszerzenia deklarujący indeksator musi zawierać nazwany parametr odbiorcy.

Poniższy przykład deklaruje indeksator IEnumerable<int> tylko do odczytu, który zwraca element na wskazanej pozycji:

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

Indeksujesz do odbiornika tak, jakby indeksator był członkiem typu odbiornika:

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

Aby uzyskać więcej informacji, zobacz Deklaracja rozszerzenia w dokumentacji językowej lub specyfikacji funkcji.

Oznaczone etykietami break i continue

Od wersji C# 15 instrukcje break i continue mogą odwoływać się do etykiety w konstrukcji otaczającej. Użyj etykiety break , aby zamknąć otaczającą pętlę lub switch instrukcję. Użyj etykiety continue , aby rozpocząć następną iterację otaczającej pętli.

Instrukcje oznaczone etykietami break i continue zastępują obejścia, których w przeciwnym razie używa się do sterowania przepływem wykonania w zagnieżdżonych pętlach, takie jak flaga logiczna ustawiana w pętli wewnętrznej, a następnie sprawdzana na każdym zewnętrznym poziomie, albo goto, które powoduje skok poza pętle. Bezpośrednie wskazanie pętli docelowej w instrukcji skoku eliminuje tę dodatkową obsługę i ułatwia zrozumienie zamierzonego przepływu sterowania.

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;
        }
    }
}

Etykieta jest umieszczana bezpośrednio na pętli lub instrukcji switch, którą oznacza. Bez etykiety break i continue zachowują swoje oryginalne działanie i odnoszą się do najbardziej wewnętrznej instrukcji, do której mają zastosowanie.

Reguła stylu IDE0410 flaguje flagę logiczną i goto wzorce, które może zastąpić oznaczona etykietą instrukcja skoku, oraz pokazuje przykłady przed i po każdej z nich.

Aby uzyskać więcej informacji, zobacz Instrukcje jump w dokumentacji językowej lub specyfikacji funkcji.

Bezpieczeństwo pamięci

C# 15 zapoczątkowuje wieloetapowe działania mające na celu na nowo zdefiniować bezpieczeństwo pamięci w tym języku. Celem jest powiązanie unsafe kontekstu z operacjami, które faktycznie uzyskują dostęp do niezarządzanej pamięci, a nie do istnienia typów wskaźników. Większość podatności związanych z bezpieczeństwem pamięci wynika z tych operacji dostępu, więc język wyraźnie je wyróżnia, aby były łatwo dostrzegalne dla recenzentów i audytorów.

W pełnym modelu element unsafe na składowej oznacza ją jako requires-unsafe: obowiązek audytu przechodzi na kod wywołujący, który musi używać tej składowej w kontekście unsafe. Zestaw decyduje się na to wymuszanie, a kompilator rejestruje wybór za pomocą atrybutu System.Runtime.CompilerServices.MemorySafetyRulesAttribute . Model dodaje również kontekstowe słowo kluczowe safe, które oznacza składowe extern i pola o jawnym układzie jako bezpieczne. Razem te reguły sprawiają, że granice potencjalnej pamięci niebezpiecznej są jawne w programie.

Pierwszy krok obejmuje relaks wskaźnika. Podczas kompilowania z wersją języka preview poniższe operacje nie wymagają już kontekstu unsafe:

  • Deklarowanie typu wskaźnika i pobieranie adresu zmiennej z operatorem & .
  • Instrukcja fixed , która przypina zmienną.
  • Konwertowanie stackalloc wyrażenia na wskaźnik.
  • Operator sizeof zastosowany do dowolnego typu niezarządzanego.

Poniższy przykład tworzy i przypina wskaźnik bez unsafe kontekstu:

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

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

Operacje uzyskujące dostęp do pamięci wskazywanej przez wskaźnik, takie jak dereferencja wskaźnika (*p), dostęp do składowej przez wskaźnik (p->member), dostęp do elementu przez wskaźnik (p[i]) oraz wywołanie wskaźnika funkcji, nadal wymagają kontekstu unsafe.

Model składowych requires-unsafe, zgoda zestawu na stosowanie zaktualizowanych reguł bezpieczeństwa pamięci oraz kontekstowe słowo kluczowe safe zostaną wprowadzone w jednej z późniejszych wersji zapoznawczych.

Aby uzyskać więcej informacji, zobacz Niebezpieczny kod, typy wskaźników i wskaźniki funkcji w dokumentacji językowej lub specyfikacji funkcji.

Informacje dodatkowe