Tl;dr:
- Гибкость:
- Из предварительной версии c# 11 удалили parameter null-checking с помощью оператора
!!
— дизайн фичи сочли неподходящим. - Полуавтоматические свойства aka возможность сделать автоматическое свойство, в котором можно обращаться к backing field с помощью ключевого слова
field
. - Модификатор для типов с областью видимости только в текущем файле.
- Первичные конструкторы для классов и структур.
- Паттерн-матчинг для списков.
- Атрибуты для Main в программах с top level statement
- Из предварительной версии c# 11 удалили parameter null-checking с помощью оператора
- Работа со строками:
- Сырые строки без экранирования внутри строки — для удобства работы с строковым представлением json, xml, html и регулярных выражений.
- Строковые литералы для UTF-8.
- Паттнерн-матчинг для
Span<char>
. - Перенос строк в выражениях интерполяции.
- Обобщенная математка aka generic math:
- Возможность перегрузки операторов с проверкой на переполнение. Теперь можно делать checked и unchecked версии операторов
++
,--
,+
,-
,/
,*
, нужная версия будет выбираться из контекста. - Оператор побитового сдвига вправо без знака
>>>
для поддержки операции в обобщенной математике.
- Возможность перегрузки операторов с проверкой на переполнение. Теперь можно делать checked и unchecked версии операторов
- Изменения в
nameof
:- nameof для параметров методов — сильно пригодится в
CallerArgumentExpressionAttribute
и атрибутах про nullable-типы вродеNotNullIfNotNullAttribute
. - Возможность обратиться к членам экземпляра другого объекта в nameof.
- nameof для параметров методов — сильно пригодится в
- Безопасность языка:
- Ключевое слово required для свойств и полей, которые должны быть инициализированы в клиентом типа при создании экземпляра.
- Новый warning CS8981 для имен типов целиком в нижнем регистре — для уменьшения вероятности использования в качестве имени типа нового зарезервированного слова.
- Автоматическая инициализация свойств структур значением по-умолчанию.
- Generic-атрибуты.
- Локальные переменные и параметры только для чтения.
- Производительность:
- Кэширование делегатов при использовании method group.
- Поля, хранящиеся по ссылке для ref struct.
params Span<T>
,params ReadOnlySpan<T>
,params IEnumerable<T>
. в объявлении методов чтобы избежать лишних неявных созданий массива в куче и копирований коллекций.

Команда C# активно работает над следующей версий языка и уже выпускает предварительные версии C# 11, которые можно попробовать вместе Visual studio 2022 Preview (и частично в Rider 2022.1).
Это обзор фич, которые прямо сейчас активно обсуждаются и находятся в работе — тестируются, разрабатываются или уточняется дизайн.
Remove parameter null-checking from C# 11
Из C# убрали добавленный в предварительной версии оператор
!!
, для проверки аргументов наnull
предлагаетсяArgumentNullException.ThrowIfNull(myString);
«Тот самый» оператор !!
для проверки параметра функции и индексатора на не null удаляют из C# 11. Предполагалось, что при использовании оператора для параметра функции в рантайме, если передано значение null
, то будет возникать исключение ArgumentNullException
:
public static void M(string s!!) // throw ArgumentNullException, если s == null
Func<string, string> s = x!! => x; // throw ArgumentNullException при вызове s(null)
public string this[string key!!] { get { ... } set { ... } }
void WarnCase<T>(string? name!!) // warn CS8995 Nullable type 'string?' is null-checked and will throw if null.
Авторы не уверены, что фича оказалась достаточно продуманно задизайнена и должна быть реализована именно таким способом. Пока рекомендованный способ проверки параметров такой:
public static void M(string myString)
{
ArgumentNullException.ThrowIfNull(myString);
// method
}
Если хочется уменьшить количество кода, то есть библиотека Fody/NullGuard, проверяющая параметры на null
, работающая с атрибутами [AllowNull]
и [NotNull]
и 3 режимами:
- В imlicit mode все параметры считаются не поддерживающими значение null, кроме помеченных
[AllowNull]
. - В explicit mode все параметры считаются поддерживающими значение null, кроме помеченных
[NotNull]
. - Если в проекте включены nullable reference types, то возможность параметра принимать значение null вычисляется исходя из сигнатуры.
Пример работы в Nullable Reference Types Mode:
public class Sample
{
// Возвращаемое значение может быть null
public string? MaybeGetValue()
{
return null;
}
// Выбрасывает InvalidOperationException, если возвращаемое значение null
public string MustReturnValue()
{
return null;
}
// Выбрасывает InvalidOperationException, если результат выполнения задачи null
public async Task<string> GetValueAsync()
{
return null;
}
// Возвращаемое задачей значение может быть null
public async Task<string?> GetValueAsync()
{
return null;
}
public void WriteValue(string arg)
{
// Выбрасывает ArgumentNullException, если arg имеет значение null
}
public void WriteValue(string? arg)
{
// Значение arg может быть null
}
public void GenericMethod<T>(T arg) where T : notnull
{
// Выбрасывает ArgumentNullException, если arg имеет значение null
}
public bool TryGetValue<T>(string key, [MaybeNullWhen(false)] out T value)
{
// Выбрасывает ArgumentNullException, если key имеет значение null
// Значение out value не проверяется
}
}
Полуавтоматические свойства (Semi-auto-properties)
Можно реализовать getter и setter для свойства без явного задания backing field. Доступ к полю будет с помощью ключевого слова
feidld
. Сокращает код.
Сейчас автоматические свойства не могут иметь логики внутри геттера и сеттера, при необходимости реализовать такую логику нужно явно объявлять в классе backing field для такого свойства. Иногда требуется больше контроля при доступе к автоматическому свойству, например, вы хотите проверить значение в сеттере или вызывать событие, которое информирует об изменении свойства.
Полуавтоматические свойства позволяют добавлять логику в свойства без явного объявления поля для этого свойства — внутри геттера и сеттера обратиться к backing fileled можно будет с помощью ключевого слова field
:
public class SemiAutoPropertiesExample
{
public int Salary
{
get { return field; }
set
{
if(value < 0)
throw new ArgumentOutOfRangeException("Зарплата не может иметь отрицательное значение");
field = value;
NotifyPropertyChanged(nameof(Salary));
}
}
public string LazyValue => field ??= ComputeValue();
private static string ComputeValue() { /*...*/ }
}
Proposal на github
Применимость: иногда
Текущий статус: In Progress
Модификатор для типов с областью видимости только в текущем файле
Можно делать невложенные типы
private
— область видимости ограничена файлом объявления. Полезно для локальных хелперов и source generators.
Генераторы исходного кода (source generators) иногда создают вспомогательные типы, которые используются только другим сгенерированным исходным кодом. Разработчики иногда тоже могут писать хелперы, которые используются только в рамках текущего файла. Сейчас такие типы можно пометить с помощью EditorBrowsableAttribute, но полностью гарантировать, что ими не воспользуются в другой части кода нельзя. Поэтому при модификации нужно будет учитывать учитывать все использования кода, а изменение реализации внутри генератора может привести к ошибкам.
Чтобы ограничить возможность использования таких вспомогательных типов текущим файлом нужно иметь модификатор доступа с областью видимости, ограниченной только текущим файлом. Для этого в C# будет разрешено использовать модификатор private
для невложенных типов:
// File1.cs
internal partial class UserType
{
internal partial void SomePartialMethod()
{
My.Own.Stuff.Zoo.Blah(); // OK
}
}
private class Zoo
{
public static void Blah() { }
}
// File2.cs
class C
{
public static void N()
=> My.Own.Stuff.Zoo.Blah(); // ERROR: Zoo isn’t defined/visible/etc.
}
Приватные типы нельзя будет использовать в неприватных членах класса как объявленный тип, возвращаемое значение или аргумент:
private class C1 { }
internal class C2 {
private void M(C1 c) { } // OK, поскольку метод M доступен только внутри C2
}
private class C3 {
internal void M(C1 c) { } // OK, поскольку C3 и его метод M доступен только в пределах файла
}
internal class C4 {
internal void M(C1 c) { } // Ошибка компиляции, поскольку C2.M доступен за пределами файла, а C1 нет
}
Ещё возможно будет использовать в рамках partial-классов приватные типы, имеющие одно имя, если такие типы объявлены в разных файлах. Это может сбивать с толку, но такой код будет компилироваться:
// file1.cs
private class Zoo { }
public partial class C {
private void M(Zoo z) { } // OK, используется Zoo из file1.cs
}
// file2.cs
private class Zoo { }
public partial class C {
private void M(Zoo z) { } // OK, используется Zoo из file2.cs
}
Proposal на github
Применимость: редко в пользовательском коде, часто в source generators
Текущее состояние: In Progress