Наступним релізом C# стане С# 9. У плані розвитку вже описано 39 пропозицій (proposals) для дев'ятої версії. Але які пропозиції будуть впроваджені і в якій версії?
Найбільш важливі функції, заплановані в C# 9, це новий тип - записи (records), диз'юнктні об'єднання (discriminated unions), поліпшення зіставлення зі зразком (pattern matching), додаткова цільова типізація існуючих конструкцій, таких як тернарні вирази і вирази об'єднання з null.
Зазначені особливості ще не знаходяться на завершальній стадії. І остаточний синтаксис може відрізнятися від описаного в 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. Багато що ще обговорюється, запропоновані функції і синтаксис/ емантика можуть бути змінені.
0 комментариев
Добавить комментарий