Що нового буде в C# 9?

  • 6 февраля, 15:43
  • 2053
  • 0

Наступним релізом C# стане С# 9. У плані розвитку  вже описано 39 пропозицій (proposals) для дев'ятої версії. Але які пропозиції будуть впроваджені і в якій версії? 

Найбільш важливі функції, заплановані в C# 9, це новий тип - записи (records), диз'юнктні об'єднання (discriminated unions), поліпшення зіставлення зі зразком (pattern matching), додаткова цільова типізація існуючих конструкцій, таких як тернарні вирази і вирази об'єднання з null. 

Зазначені особливості ще не знаходяться на завершальній стадії. І остаточний синтаксис може відрізнятися від описаного в proposals.

Що нового буде в C# 9?

Записи

Записи (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 комментариев
Сортировка:
Добавить комментарий