Работа мечты в один клик 💼

💭Мечтаешь работать в Сбере, но не хочешь проходить десять кругов HR-собеседований? Теперь это проще, чем когда-либо!
💡AI-интервью за 15 минут – и ты уже на шаг ближе к своей новой работе.
Как получить оффер? 📌 Зарегистрируйся 📌 Пройди AI-интервью 📌 Получи обратную связь сразу же!
HR больше не тянут время – рекрутеры свяжутся с тобой в течение двух дней! 🚀
Реклама. ПАО СБЕРБАНК, ИНН 7707083893. Erid 2VtzquscAwp
Следующим релизом C# станет С# 9. В плане развития языка уже описано 39 предложений (proposals) для девятой версии. Но какие предложения будут внедрены и в какой версии? Только команда разработчиков .Net может ответить на этот вопрос. Наиболее важные функции, запланированные в C# 9, это новый тип – записи (records), дизъюнктные объединения (discriminated unions), улучшение сопоставления с образцом (pattern matching), дополнительная целевая типизация существующих конструкций, таких как тернарные выражения и выражения объединения с null
. Бассам Алугили в публикации Глубокое погружение в C # 9 (англ.) описал ключевые изменения, которые могут появиться в новой версии языка. Мы представляем незначительно сокращённый перевод статьи.

Указанные особенности ещё не находятся на заключительной стадии. И окончательный синтаксис может отличаться от описанного в proposals.
Записи
Записи (records) представляют собой новую упрощенную форму объявления классов C# и структурных типов. Описание этого нового типа дано на GitHub. Актуальная версия предложения изложена здесь.
Записи номинально типизированы и могут иметь методы, свойства, операторы и т. д., позволяют проводить структурное сравнение. По умолчанию свойства записи доступны только для чтения. Записи могут быть Value Type
или Reference Type
.
Запись может быть определена следующим образом:
public class Point3D
{
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
}
В случае неизменного типа предлагается использовать новый модификатор initonly
, который можно применять к свойствам и полям:
public class Point3D
{
public initonly int X { get; }
public initonly int Y { get; }
public initonly int Z { get; }
...
...
}
Создание объекта записи:
void DoSomething()
{
var point3D = new Point3D()
{
X = 1,
Y = 1,
Z =1
};
}
Использование записей с with-выражениями
Во вводной части заявки на внедрение записей в качестве нового элемента языка предлагаются with
-выражения. С помощью with
можно сразу изменять копию объекта записи:
public class Demo
{
public void DoIt()
{
var point3D = new Point3D() { X = 1, Y = 1, Z =1 };
Console.WriteLine(point3D);
}
}
var newPoint3D = point3D with {X = 42};
Созданная новая точка newPoint3D
аналогична существующей point3D
, но значение X изменено на 42.
Равенство
Записи сравниваются по структуре, а не по ссылке:
void DoSomething()
{
var point3D1 = new Point3D()
{
X = 1,
Y = 1,
Z =1
};
var point3D2= new Point3D()
{
X = 1,
Y = 1,
Z =1
};
var compareRecords = point3D1 == point3D2; // true
}
Дизъюнктное объединение
Термин дизъюнктное объединение (discriminated union, disjoint union) заимствован из математики. Например, есть связанные множестваA0 = {(5,0), (6,1)}
и A1 = {(7,2)}
. Дизъюнктное объединение заключается в объединении непересекающихся «копий» множеств:
A0 ⊔ A1 = {(5,0), (6,1), (7,2)}
Предлагаемая функциональность дизъюнктного объединения в C#9 аналогична F# и позволяет определять типы, которые могут содержать любое количество различных типов данных, объединения полезны для различных неоднородных данных и создания простых иерархических структур.
Пример дизъюнктного объединения в F#:
type Person = {firstname:string; lastname:string} // определяем запись
type ByteOrBool = Y of byte | B of bool
type MixedType =
| P of Person // используем запись, введенную выше
| U of ByteOrBool // используем запись, введенную выше
let unionRecord = MixedType.P({firstname="Bassam"; lastname= "Alugili"});
let unionType1 = MixedType.U( B true); // Boolean type
let unionType2 = MixedType.U( Y 86uy); // Byte type
Возьмём для дизъюнктного объединения в С# 9 пару сущностей:
// Определяем запись
public class Person
{
public initonly string Firstname { get; }
public initonly string Lastname { get; }
};
enum class ByteOrBool { byte Y; bool B;} // Возможный вариант синтаксиса
Во втором случае мы хотим тип, который отражает все возможные целые числа и все возможные булевы значения:

В нашем случае новый тип ByteOrBool – это «сумма» байтового и логического типов. Как и в F#, суммарный тип называется discriminated union
(дизъюнктное объединение).
enum class MixedType
{
Person P;
ByteOrBool U;
}
Создание экземпляра объединения:
var person = new Person()
{
Firstname = ”Bassam”;
Lastname = “Alugili”;
};
var unionRecord = new MixedType.P(person); // Record C# 9
var unionType1 = new MixedType.U( B true); // Boolean type
var unionType2 = new MixedType.U( Y 86uy); // Byte type
Использование дизъюнктных объединений
Приведенные ниже примеры демонстративные, только для лучшего понимания предлагаемых новшеств.
1. Обработка исключений, как в Java:
try
{
…
…
}
catch (CommunicationException | SystemException ex)
{
// Здесь обрабатываем CommunicationException и SystemException
}
2. Ограничение типа:
public class GenericClass<T> where T : T1 | T2 | T3
Универсальный класс может принадлежать одному из типов T1
, T2
или T3
.
3. Гетерогенные коллекции:
var crazyCollectionFP = new List<int|double|string>{1, 2.3, "bassam"};
4. Комбинация переменных/значений/выражений разных типов через операторы ? :
, ??
или выражение switch
:
var result = x switch { true => "Successful", false => 0 };
Тип результата здесь будет string | int
.
5. Если несколько перегрузок какого-либо метода имеют одинаковые реализации, их можно объединить. Например, следующий набор
void logInput(int input) => Console.WriteLine($"The input is {input}");
void logInput(long input) => Console.WriteLine($"The input is {input}");
void logInput(float input) => Console.WriteLine($"The input is {input}");
может быть заменен на единственную строку:
void logInput(int|long|float input) => Console.WriteLine($"The input is {input}");
6. Можно использовать подход для возвращаемых типов:
public int|Exception Method() // возвращение исключения
public class None {}
public typealias Option<T> = T | None; // Option type
public typealias Result<T> = T | Exception; // Result type
Оператор объединения с null (??)
Речь идет о разрешении неявного преобразования для выражений, в которых происходит объединение с null
. Вот пример в C# 8:
void M(List<int> list, uint? u)
{
IEnumerable<int> x = list ?? (IEnumerable<int>)new[] { 1, 2 }; // C# 8
var l = u ?? -1u; // C# 8
}
В C# 9 тот же код будет выглядеть так:
void M(List<int> list, uint? u)
{
IEnumerable<int> x = list ?? new[] { 1, 2 }; // C# 9
var l = u ?? -1; // C# 9
}
Выражение new
Рассмотрим пример из официального предложения.
IEnumerable<KeyValuePair<string, string>> Headers = new[]
{
new KeyValuePair<string, string>("Foo", foo),
new KeyValuePair<string, string>("Bar", bar),
}
Приведённый код может быть упрощен до следующего
IEnumerable<KeyValuePair<string, string>> Headers = new KeyValuePair<string, string>[]
{
new("Foo", foo),
new("Bar", bar),
}
Но вам всё равно нужно повторно указать тип после инициализации поля/свойства. Самое близкое, что можно получить, это что-то вроде:
IEnumerable<KeyValuePair<string, string>> Headers = new[]
{
new KeyValuePair<string, string>("Foo", foo),
new("Bar", bar),
}
Для полноты картины можно предложить также сделать new[]
выражением с типом целевого объекта.
IEnumerable<KeyValuePair<string, string>> Headers = new[]
{
new("Foo", foo),
new("Bar", bar),
}
Атрибут вызываемого выражения
Идея состоит в том, чтобы позволить вызывающему объекту «структурировать» выражения, переданные на место вызова. Конструктор атрибута примет строковый аргумент, определяющий имя аргумента для строкового преобразования.
public static class Verify {
public static void InRange(int argument, int low, int high,
[CallerArgumentExpression("argument")] string argumentExpression = null,
[CallerArgumentExpression("low")] string lowExpression = null,
[CallerArgumentExpression("high")] string highExpression = null) {
if (argument < low) {
throw new ArgumentOutOfRangeException(paramName: argumentExpression, message: $ " {argumentExpression} ({argument}) cannot be less than {lowExpression} ({low}).");
}
if (argument > high) {
throw new ArgumentOutOfRangeException(paramName: argumentExpression, message: $ "{argumentExpression} ({argument}) cannot be greater than {highExpression} ({high}).");
}
}
public static void NotNull < T > (T argument,
[CallerArgumentExpression("argument")] string argumentExpression = null)
where T: class {
if (argument == null) throw new ArgumentNullException(paramName: argumentExpression);
}
}
// CallerArgumentExpression: преобразует выражения в строку!
Verify.NotNull(array); // paramName: "массив"
// paramName: "индекс"
// Сообщение об ошибке из-за неверного Index:
"index (-1) cannot be less than 0 (0).", or
// "index (6) cannot be greater than array.Length - 1 (5)."
Verify.InRange(index, 0, array.Length - 1);
Упрощённая default-деконструкция кортежа:
В C# 7 сопоставляем каждой переменной default
:
(int x, string y) = (default, default);
В C# 9 проще:
(int x, string y) = default;
Свободный порядок модификаторов ref и partial
При объявлении класса можно указывать partial
перед ref
:
public ref partial struct {} // C# 7
public partial ref struct {} // C# 9
Проверка на null
Задача – упростить стандартную null проверку параметров, используя небольшую аннотацию параметров. Эта функция относится к улучшению качества кода. Подробнее смотрите в заметках об обсуждении.
// Ранее в C# 1..7.x
void DoSomething(string txt)
{
if (txt is null)
{
throw new ArgumentNullException(nameof(txt));
}
…
}
// Кандидат для C# 9
void DoSomething (string txt!)
{
…
}
Пустые параметры для лямбда-выражений
Идея состоит в том, чтобы разрешить при вводе лямбда-выражений множественные объявления параметров с именем _
. В этом случае параметры являются «сброшенными» (discard) и не могут использоваться внутри лямбды:
Func zero = (_,_) => 0;
(_,_) => 1, (int, string) => 1, void local(int , int);
Атрибуты локальных функций
Идея состоит в том, чтобы позволить атрибутам быть частью объявления локальной функции.
Пример:
static void Main(string[] args)
{
static bool LocalFunc([NotNull] data)
{
return true;
}
}
Еще один пример использования – с EnumeratorCancellation
для параметра CancellationToken
локальной функции, реализующей асинхронный итератор, что часто встречается при реализации операторов запросов.
public static IAsyncEnumerable Where(this IAsyncEnumerable source, Func predicate)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (predicate == null)
throw new ArgumentNullException(nameof(predicate));
return Core();
async IAsyncEnumerable<T> Core([EnumeratorCancellation] CancellationToken token = default)
{
await foreach (var item in source.WithCancellation(token))
{
if (predicate(item))
{
yield return item;
}
}
}
}
Native Int
Предлагается ввести набор нативных типов (nint
, nuint
). Планируется, что дизайн новых типов данных позволит одному исходному файлу C# использовать 32- или 64-разрядные хранилища в зависимости от типа платформы хоста и настроек компиляции.
Тип определяется ОС:
nint nativeInt = 55; // 4 байта при компиляции в 32 бит системе
nint nativeInt = 55; // 8 байт при компиляции в 64 бит системе с x64 настройками компиляции
Указатели на функции
Термин «указатель на функцию» многим известен из С/С++. В переменной хранится адрес функции, которая впоследствии может быть вызвана через указатель этой функции. Указатели на функции можно вызывать и передавать им аргументы, как при обычном вызове функции. Подробнее читайте в описании предложения.
Указатель на функцию C# позволяет объявлять указатели на функции с использованием синтаксиса func*
. Это похоже на синтаксис, используемый при объявлении делегатов:
unsafe class Example
{
void Example(Action<int> a, delegate*<int, void> f)
{
a(42);
f(42);
}
}
Заключение
Итак, вы прочитали о кандидатах в C# 9. Также вы можете посмотреть список рабочих изменений C# NEXT. Только если функции-кандидаты находятся в ветви master
, они будут выпущены в следующей версии. Многое ещё обсуждается, предложенные функции и синтаксис/семантика могут быть изменены.
Комментарии