Обзор новых возможностей С# 7.3

image

В мае вышла новая версия Visual Studio, где была добавлена поддержка новой версии языка — 7.3. Язык продолжает планомерно развиваться и несмотря на то, что релиз C# 7.3 является минорным в нем исправлено несколько застарелых проблем, живущих в языке начиная с первой и второй версии. Давайте посмотрим более подробно, что именно было добавлено.

Сравнение кортежей


Развивается поддержка кортежей в языке и если в предыдущих версиях стало возможным возвращать из функции несколько значений, то теперь кортежи можно сравнивать между собой. Раньше приходилось писать что-то вроде:

(int, int) t1 = (1, 2);
(int, int) t2 = (1, 2);

if (t1.Item1 == t2.Item1 && t1.Item2 == t2.Item2)
{
     Console.WriteLine("t1 и t2 равны");
}

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

if (t1 == t2)

Отметим, что имена не учитываются — важен порядок элементов в кортеже. То есть следующие два кортежа не равны:

var t1 = (celsius: 5, fahrenheit: 41);
var t2 = (fahrenheit: 41, celsius: 5);

А вот эти равны:

var t1 = (celsius: 5, fahrenheit: 41);
var t2 = (celsius: 5, fahrenheit: 41);

Сняты ограничения на переменные выражения в инициализаторах и LINQ запросах


Переменные выражения теперь можно использовать непосредственно при инициализации поля в классе, в конструкторе и LINQ запросе. То есть следующий код скомпилируется и выведет на консоль 65535:

class Program
{
    private static int magicNumber_ = int.TryParse("65535", out var i) ? i : 0;

    public static void Main()
    {
        Console.WriteLine(magicNumber_);
    }
}

Хотя лично мне сама идея использования out и ref значений в качестве аргументов функции не нравится. В моем идеализированном представлении входные параметры должны получать значения, а не возвращать результат работы. Но такая возможность в языке есть и ей можно пользоваться.

Новые виды ограничений для обобщенных типов и функций


Теперь у вас имеется больше контроля над обобщенными типами. Разработчикам часто не хватало только двух ограничений class и struct. Поэтому в новой версии языка добавлено еще три: enum, delegate и ограничение для “blittable” типов — unmanaged. С первыми двумя все и так ясно, достаточно лишь только посмотреть на пример обобщенного метода:

public static string Method<T>(T arg) 
            where T : Enum
    {
        return arg.ToString();
    }

Третий тип ограничений — unmanaged подразумевает, что тип не является ссылочным и не содержит ссылочных полей на любом уровне вложенности. На практике все сводится к использованию данного типа при взаимодействии с низкоуровневым неуправляемым кодом.

Возможность указать атрибуты для полей из автоматически реализуемых свойств.


Здесь все понятно из заголовка. Многим доводилось писать код вроде:

[Serializable]
public class Foo
{
    [NonSerialized]
    private string mysSecret_;

    public string MySecret
    {
        get
        {
            return mysSecret_;
        }
        set
        {
            mysSecret_ = value;
        }
    }

    public string SecondProperty
    {
        get;
        set;
    }
}

Автоматически реализуемое свойство для MySecret использовать не получится, если вы хотите его исключить из сериализации. Однако в новой версии языка можно написать так:

[Serializable]
public class Foo
{
    [field: NonSerialized]
    public string MySecret
    {
        get;
        set;
    }

    public string SecondProperty
    {
        get;
        set;
    }
}

Таким образом вы можете применить любой атрибут для поля из автоматически сгенерированного свойства. Это похоже на аналогичный функционал для событий доступный еще с первой версии языка.

Переприсвоение для локальных переменных ссылок


Переменные ссылки, которые были введены в язык ранее теперь ведут себя более естественно. Сейчас такой код скомпилируется:

int i = 22;
int j = 33;
ref int i2 = ref i;
i2 = ref j;
j = 44;
Console.WriteLine(i2); // Выведет на экран 44

Раньше на строке с присвоением переменной (i2 = ref j) была бы ошибка.

Инициализация массива при размещении в стеке


Когда приходится работать с большим количеством маленьких массивов их можно разместить в стеке. С одной стороны такое решение работает быстрее и не нагружает лишний раз GC, но с другой, размеры стека ограничены так что нужно аккуратно работать с кодом. В C# размещать массив стеке можно с помощью ключевого слова stackalloc, но инициализировать его непосредственно при объявлении было нельзя. В 7.3 устранили это ограничение, так что теперь можно записать:

Span<int> arr1 = stackalloc int[3];    // Такая конструкция была доступна и раньше
Span<int> arr2 = stackalloc int[3] { 1, 2, 3 };
Span<int> arr3 = stackalloc int[] { 1, 2, 3 };
Span<int> arr4 = stackalloc[] { 1, 2, 3 };

Span был добавлен совсем недавно в версии языка 7.2 и он позволяет ссылаться на хранимые в стеке значения. Естественно, что все вышесказанное можно переписать и без Span с помощью unsafe кода. В таком случае Span<int> нужно будет заменить на int*.

Улучшения в работе правил разрешения перегрузки


Рассмотрим следующий код получения максимального времени создания для списка файлов.

var files = new string[] 
    {
        @"C:\Temp\first.txt",
        @"C:\Temp\second.txt"
    };
var maxDate = files.Max(System.IO.File.GetCreationTime);

Формально все написано по правилам языка, но этот код не компилируется. Причем если переписать последнюю строчку в виде:

var maxDate = files.Max(x => System.IO.File.GetCreationTime(x));

Все будет компилироваться и работать. В новой версии языка в этом нет необходимости и изначальный пример будет работать как ожидалось. Причина в том, что были внесены изменения в работу правил разрешения перегрузки. По факту исправлен дефект компилятора, он стал умнее, а код теперь писать с большим комфортом.

А также...


Есть еще несколько нововведений, однако мне они показались не настолько интересными для широкой публики. Подробнее про них можно почитать в первоисточнике.
Метки:
.net, c# 7

Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.