ProXiT
Когда закрывается одна дверь к счастью, тут же открывается другая. Но мы часто так долго смотрим на первую, что не замечаем вторую.

22 новых фичи C# — каким будет C# 11+

Tl;dr:

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

Share

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *