﻿# Обзор нововведений в C\# 10

В данной статье мы рассмотрим новую версию языка C\# 10, которая включает в себя небольшой список изменений относительно C\# 9\. Ниже приведены их описания вместе с поясняющими фрагментами кода\. Давайте их рассмотрим\.

![0875_OverviewCSharp10_ru/image1.png](https://import.viva64.com/docx/blog/0875_OverviewCSharp10_ru/image1.png)

## Изменения структур

### Инициализация полей структуры

Теперь в структурах можно задать инициализацию полей и свойств:

```cpp
public struct User
{
    public User(string name, int age)
    {
        Name = name;
        Age = age;
    }
    string Name { get; set; } = string.Empty;
    int Age { get; set; } = 18;
}
```

### Объявление конструктора структуры без параметров

В C\# 10 станет доступно создание конструктора без параметров для структур:

```cpp
public struct User
{
    public User()
    {

    }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
    }

    string Name { get; set; } = string.Empty;
    int Age { get; set; } = 18;
}
```

**Важно\.** Использовать конструктор без параметров можно только тогда, когда все поля и/или свойства имеют инициализаторы\. Например, если не задать инициализатор _Age_, возникнет ошибка:

_Error CS0843: Auto\-implemented property 'User\.Age' must be fully assigned before control is returned to the caller\._

## Применение выражения with к структуре

Новая версия C\# делает доступным использование выражения _with_ со структурами, как ранее это можно было сделать с записями\. Пример:

```cpp
public struct User
{
    public User()
    {

    }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public string Name { get; set; } = string.Empty;
    public int Age { get; set; } = 18;
}

User myUser = new("Chris", 21);
User otherUser = myUser with { Name = "David" };
```

Очевидно, что изменяемое свойство \(в данном случае поле _Name_\) должно быть с публичным модификатором доступа\.

## Глобальное использование using

Данное нововведение позволит использовать директиву _using_ с охватом всего проекта\. Для этого необходимо перед фразой _using_ добавить ключевое слово _global_:

```cpp
global using "Пространство имен"
```

Таким образом, можно не дублировать в разных файлах одни и те же пространства имён, используя директиву _using_\.

**Важно\.** Применять данную конструкцию следует ПЕРЕД включениями _using_, которые объявлены без использования _global_\. Пример:

```cpp
global using System.Text;
using System;
using System.Linq;
using System.Threading.Tasks;
// Корректная запись
```

В ином случае:

```cpp
using System;
using System.Linq;
using System.Threading.Tasks;
global using System.Text;
// Error CS8915
// A global using directive must precede
// all non-global using directives.
```

Если произошло так, что вы повторно включили одно пространство имён при условии, что оно было ранее включено с использованием ключевого слова _global_, то об этом будет сообщено средой разработки \(_IDE: 0005: Using directive is unnecessary_\)\.

## namespace для всего файла

Бывает такое, что пространство имён необходимо использовать во всём файле, но это может немного сдвинуть отступы текста программы\. Теперь для решения этой проблемы добавлена возможность использования _namespace_ для всего файла\. Для этого необходимо написать _namespace_ без использования блочных фигурных скобок:

```cpp
using System;
using System.Linq;
using System.Threading.Tasks;

namespace TestC10;

public class TestClass
{
    ....
}
```

Ранее необходимо было тянуть на весь файл открытый блок _namespace_:

```cpp
using System;
using System.Linq;
using System.Threading.Tasks;

namespace TestC10
{
    public class TestClass
    {
        ....
    }
}
```

Очевидно, указать в файле можно лишь один такого рода _namespace_\. То есть данная запись некорректна:

```cpp
namespace TestC10;
namespace MyDir;
// Error CS8954
// Source file can only contain
// one file-scoped namespace declaration
```

как и запись такого вида:

```cpp
namespace TestC10;
namespace MyDir
{
    ....
}
// Error CS8955
// Source file can not contain both
// file-scoped and normal namespace declarations.
```

## Изменение записей

### Ключевое слово class

В версии языка C\# 10\.0 было добавлено необязательное ключевое слово _class_ у записей, которое лишь помогает при чтении кода определить принадлежность записи к ссылочному типу\.

То есть две следующие записи идентичны:

```cpp
public record class Test(string Name, string Surname);
public record Test(string Name, string Surname);
```

### Структуры записей

Также теперь можно создавать структуры записей:

```cpp
record struct Test(string Name, string Surname)
```

По умолчанию, свойства данной записи можно изменять, в отличие от стандартной _record_, в которой модификатор изменения полей – _init_:

```cpp
string Name { get; set; }
string Surname { get; set; }
```

К данной структуре можно применить свойство _readonly_, тогда доступ к полям будет подобен обычной записи:

```cpp
readonly record struct Test(string Name, string Surname);
```

где свойства записаны как:

```cpp
string Name { get; init; }
string Surname { get; init; }
```

Равенство двух объектов структуры записей схоже с равенством двух обычных структур – равенство истинно, если эти два объекта хранят одни и те же данные:

```cpp
var firstRecord = new Person("Nick", "Smith");
var secondRecord = new Person("Robert", "Smith");
var thirdRecord = new Person("Nick", "Smith");

Console.WriteLine(firstRecord == secondRecord);
// False
Console.WriteLine(firstRecord == thirdRecord);
// True
```

Стоит отметить, что для структур записей не генерируется конструктор копирования\. Если определить его и использовать ключевое слово _with _при инициализации нового объекта, то вызываться будет оператор присваивания, вместо конструктора копирования \(как это происходит при работе с _record class_\)\.

### Запечатывание метода ToString

Как писал мой коллега в статье о [нововведениях в C\# 9](https://pvs-studio.ru/ru/blog/posts/csharp/0860/), у записей имеется метод _ToString_, который можно переопределить\. Но при наследовании есть одна особенность: переопределенный вами метод _ToString_ не будет унаследован потомками от родительской записи\. Для того чтобы потомки записей наследовали метод _ToString_, стало доступно использование ключевого слова _sealed_, которое запрещает компилятору генерировать имплементацию метода _ToString_ у дочерних записей\. Данное ключевое слово указывается при переопределении метода _ToString_:

```cpp
public sealed override string ToString()
{
    ....
}
```

Создадим следующую запись, включая переопределение метода _ToString_:

```cpp
public record TestRec(string name, string surname)
{
    public override string ToString()
    {
        return $"{name} {surname}";
    }
}
```

И наследуем от него вторую запись:

```cpp
public record InheritedRecord : TestRec
{
    public InheritedRecord(string name, string surname)
    :base(name, surname)
    {

    }
}
```

Теперь создадим по экземпляру каждой из записей и напечатаем результат в консоль:

```cpp
TestRec myObj = new("Alex", "Johnson");
Console.WriteLine(myObj.ToString());
// Alex Johnson

InheritedRecord mySecObj = new("Thomas", "Brown");
Console.WriteLine(mySecObj.ToString());
// inheritedRecord { name = Thomas, surname = Brown}
```

Как видно из результата выше, метод _ToString_ не был наследован записью _InheritedRecord_\.

Немного изменим запись _TestRec_, добавив ключевое слово _sealed_:

```cpp
public record TestRec(string name, string surname)
{
    public sealed override string ToString()
    {
        return $"{name} {surname}";
    }
}
```

Заново создадим два экземпляра записей и напечатаем результат в консоль:

```cpp
TestRec myObj = new("Alex", "Johnson");
Console.WriteLine(myObj.ToString());
// Alex Johnson

InheritedRecord mySecObj = new("Thomas", "Brown");
Console.WriteLine(mySecObj.ToString());
// Thomas Brown
```

И\.\. ура\! Метод _ToString_ был наследован в записи _InheritedRecord_\.

## Упрощение доступа к вложенным полям и свойствам шаблонных свойств

Версия языка C\# 8\.0 предоставила возможность использования шаблонных свойств, с помощью которых можно удобно сопоставлять поля и/или свойства какого\-либо объекта с необходимыми значениями\.

Ранее, если необходимо было проверить какое\-либо вложенное свойство, то приходилось громоздить конструкцию:

```cpp
....{property: {subProperty: pattern}}....
```

Сейчас достаточно лишь привычного добавления точек между свойствами:

```cpp
....{property.subProperty: pattern}....
```

Рассмотрим изменение на примере метода взятия 4 первых символов имени\.

```cpp
public record TestRec(string name, string surname);

string TakeFourSymbols(TestRec obj) => obj switch
{
    // старый способ:
    //TestRec { name: {Length: > 4} } rec => rec.name.Substring(0,4),

    // новый способ:
    TestRec { name.Length: > 4 } rec => rec.name.Substring(0,4),
    TestRec rec => rec.name,
};
```

Как видно из примера выше, новый тип обращения к свойствам проще и понятнее, чем был раньше\.

## Интерполирование константных строк

Ранее данная возможность не поддерживалась, но в новой версии языка C\# можно будет интерполировать и константные строки:

```cpp
const string constStrFirst = "FirstStr";
const string summaryConstStr = $"SecondStr {constStrFirst}";
```

**Интересный факт\.** Данное нововведение относится к интерполированию строк, то есть добавление константного символа не допускается:

```cpp
const char a = 'a';
const string constStrFirst = "FirstStr";
const string summaryConstStr = $"SecondStr {constStrFirst} {a}";
// Error CS0133
// The expression being assigned to
// 'summaryConstStr' must be constant
```

## Одновременное использование объявленных и инициализируемых переменных при деконструировании

В старом варианте вызова деконструктора можно использовать ИЛИ объявленные переменные \(все объявлены\), ИЛИ переменные, которые мы инициализируем прямо при вызове \(все НЕ объявлены\):

```cpp
Car car = new("VAZ 2114", "Blue");

var (model, color) = car;
// Инициализация

string model = string.Empty;
string color = string.Empty;
(model, color) = car;
// Присваивание
```

Новая версия языка допускает одновременное использование как объявленных ранее, так и не объявленных переменных при деконструировании:

```cpp
string model = string.Empty;
(model, var color) = car;
// Инициализация и присваивание
```

В версии C\# 9 возникала ошибка:

_Error CS8184: A deconstruction cannot mix declarations and expressions on the left\-hand\-side\._

## Заключение

Как говорилось ранее, список изменений не столь велик, как в версии C\# 9\. Одни изменения упрощают работу, а другие разблокируют ранее недоступные возможности\. Развитие не стоит на месте, а мы будем ждать новых обновлений языка C\#\.

Если вы еще не ознакомлены с нововведениями версии C\# 9, то о них мы тоже подробно рассказывали в отдельной [статье](https://pvs-studio.ru/ru/blog/posts/csharp/0860/)\.

Если вы хотите ознакомиться с первоисточником, можете прочесть [документацию Microsoft](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10)\.