Новые возможности C# 15

C# 15 включает следующие новые функции. Попробуйте эти функции, используя последнюю инсайдерскую версию Visual Studio 2026 или предварительный пакет SDK .NET 11:

C# 15 — это последний выпуск предварительной версии C#. Предварительная версия .NET 11 поддерживает C# 15. Дополнительные сведения см. в разделе версии языка C# .

Вы можете скачать последний пакет SDK предварительной версии .NET 11 на странице загрузки .NET. Вы также можете скачать инсайдерскую версию Visual Studio 2026, включая предварительную версию SDK для .NET 11.

Страница "Новые возможности C#" добавляет новые функции, когда они доступны в общедоступных предварительных версиях. Раздел рабочего набора на странице статуса функций Roslyn отслеживает, когда предстоящие функции объединяются в основную ветвь.

Критические изменения, внесенные в C# 15, можно найти в нашей статье о критических изменениях.

Замечание

Мы заинтересованы в ваших отзывах об этих функциях. Если вы найдете проблемы с любым из этих новых функций, создайте новую проблему в репозитории dotnet/roslyn.

Аргументы выражения коллекции

Аргументы можно передать в конструктор подлежащей коллекции или фабричный метод, используя элемент with(...) в качестве первого элемента в выражении коллекции. Эта возможность позволяет вам указывать емкость, компараторы или другие параметры конструктора непосредственно в синтаксисе выражения коллекции.

В следующем примере показано, как передать аргумент емкости конструктору List<T> и компаратору 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

Дополнительные сведения о аргументах выражений коллекции см. в статье справочника по языку о выражениях коллекции или спецификации компонентов. Сведения об использовании аргументов выражения коллекции в инициализаторах коллекции см. в разделе "Инициализаторы объектов и коллекций".

Типы объединения

C# 15 представляет типы объединения, которые означают значение, являющееся одним из нескольких типов-кейсов. Объявите объединение с ключевым словом 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);

Объединения предоставляют неявные преобразования из каждого типа варианта, и компилятор гарантирует, что switch выражения охватывают все варианты типа:

Pet pet = new Dog("Rex");

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

Среда выполнения включает типы UnionAttribute и IUnion начиная с .NET 11 preview 5. Некоторые функции из спецификации предложения пока не реализованы. Эти функции будут доступны в будущих предварительных версиях.

Дополнительные сведения см. в разделе "Типы профсоюзов " в справочнике по языку или спецификации компонентов.

Закрытые иерархии

Начиная с C# 15, к классу можно применить модификатор closed, чтобы объявить закрытую иерархию. Закрытый класс может быть производным только от декларируемой сборки, которая исправляет набор прямых потомков во время компиляции:

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

Поскольку компилятор знает всех прямых потомков, switch выражение, которое обрабатывает все из них, является полным и не требует ветви по умолчанию:

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

Модификатор closed — это контекстное ключевое слово. Класс closed неявно является abstract и не может сочетаться с sealed, static или явным модификатором abstract. Производный не является транзитивным: не закрытый потомок закрытого класса по-прежнему может быть производным от других сборок. Чтобы расширить исчерпывающую проверку иерархии, пометьте промежуточных потомков closed .

Дополнительные сведения см. в шаблонах закрытой модификатора и закрытой иерархии в справочнике по языку или спецификации функции.

Индексаторы расширений

Начиная с C# 15, можно объявить индексаторы в блоке extension . Индексаторы расширений позволяют индексировать приемник, как будто индексатор был объявлен в типе приемника. Так как индексаторы всегда являются элементами экземпляров, блок расширения, объявляющий индексатор, должен предоставить именованный параметр приемника.

В следующем примере объявляется индексатор IEnumerable<int>, доступный только для чтения, который возвращает элемент в указанной позиции:

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

Индексировать приемник, как будто индексатор был членом типа приемника:

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

Дополнительные сведения см. в объявлении расширения в справочнике по языку или спецификации компонентов.

Помечены break и continue

Начиная с C# 15, операторы break и continue могут указывать метку во внешней конструкции. Используйте метку break , чтобы выйти из включающего цикла или switch инструкции. Используйте метку continue , чтобы начать следующую итерацию включающего цикла.

Помеченные break и continue заменяют обходные приёмы, к которым иначе пришлось бы прибегнуть для управления потоком выполнения во вложенных циклах, например логический флаг, который устанавливают во внутреннем цикле, а затем проверяют на каждом внешнем уровне, или goto, который позволяет перескочить через циклы. Если указать целевой цикл непосредственно в операторе перехода, это избавляет от лишних вспомогательных действий и делает подразумеваемый поток управления более понятным.

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

Поместите метку непосредственно в цикл или оператор switch, к которым она относится. Без метки break и continue сохраняют своё исходное поведение и относятся к ближайшей применимой инструкции.

Правило стиля IDE0410 помечает логический флаг и goto шаблоны, которые может заменить помеченная инструкция перехода, и показывает примеры до и после каждого из них.

Дополнительные сведения см. в инструкциях Jump в справочнике по языку или спецификации компонентов.

Безопасность памяти

C# 15 начинает многорелизную инициативу по переосмыслению безопасности работы с памятью в языке. Цель заключается в том, чтобы связать unsafe контекст с операциями, которые фактически обращаются к неуправляемой памяти, а не к существованию типов указателей. Большинство уязвимостей безопасности памяти происходят из этих операций доступа, поэтому язык выделяет их для рецензентов и аудиторов.

В полной модели элемент, помеченный unsafe, считается requires-unsafe: обязанность по аудиту переходит к вызывающей стороне, которая должна использовать этот элемент в контексте unsafe. Сборка явно включает применение этого правила, а компилятор фиксирует этот выбор с помощью атрибута System.Runtime.CompilerServices.MemorySafetyRulesAttribute. Модель также добавляет контекстное ключевое слово safe, которое помечает члены extern и поля с явной компоновкой как безопасные. Вместе эти правила явно определяют границы потенциально небезопасных операций с памятью во всей программе.

Первый шаг включает в себя расслабление указателя. При компиляции с языковой preview версией следующие операции больше не требуют контекста unsafe :

  • Объявление типа указателя и взятие адреса переменной с помощью оператора &.
  • Инструкция fixed , которая закрепляет переменную.
  • Преобразование stackalloc выражения в указатель.
  • Оператор sizeof применяется к любому неуправляемому типу.

В следующем примере создается и закрепляется указатель без контекста unsafe :

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

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

Операции, обращающиеся к памяти, на которую указывает указатель, такие как разыменование указателя (*p), доступ к члену через указатель (p->member), доступ к элементу через указатель (p[i]), а также вызов функции через указатель, по-прежнему требуют контекста unsafe.

C# 15 также добавляет выражение, unsafe(expression)которое устанавливает небезопасный unsafe контекст для одного выражения. Полезно, когда unsafe блок не может отображаться синтаксически, например инициализатор полей, инициализатор конструктора или catch фильтр:

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

Как и для остальных компонентов предварительной версии функции безопасности памяти, выражения unsafe требуют версию языка preview и параметр компилятора AllowUnsafeBlocks.

Компилятор также распознает контекстное ключевое слово safe как модификатор для членов extern и полей с явным расположением. Тем не менее, требуемая небезопасная модель-член и сборка в соответствии с обновленными правилами безопасности памяти пока недоступны, поэтому safe в unsafe настоящее время не влияет на вызывающих абонентов.

Дополнительные сведения см. в разделе "Небезопасный код", "типы указателей" и указатели функций в справочнике по языку или спецификации функций.

См. также