Нововведення у С# 7.0 - Блог ITVDN
ITVDN: курси програмування
Відеокурси з
програмування
УКР
  • РУС
  • УКР

    Вибери свою IT спеціальність

    Підписка
    УКР
    • РУС
    • УКР

    Нововведення у С# 7.0

    advertisement advertisement

    В этой статье мы расскажем о нововведениях в языке C# 7.0, которые были представлены в марте 2017 года как часть релиза Visual Studio 2017.

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

    Если вас интересует процесс разработки, который привел к этому набору функций, вы можете найти заметки, предложения и множество обсуждений на эту тему на сайте C# language design GitHub.

    Если данная информация кажется вам знакомой, это только потому, что релиз предварительной версии состоялся в августе прошлого года. В окончательной версии C# 7.0 изменились некоторые детали, некоторые из них - из-за отличных отзывов на указанную ранее статью.

    Получайте удовольствие от C# 7.0 и удачного хакинга!

    Out переменные

    В более ранних версиях C# использование out параметров является не таким легким, как нам хотелось бы. Прежде чем вызвать метод с out параметрами, сначала необходимо объявить переменные, чтобы перейти к нему. Поскольку вы обычно не инициализируете эти переменные (они все равно будут перезаписаны методом), вы также не можете использовать ключевое слово var, но вам нужно указать полный тип:

    public void PrintCoordinates(Point p)
    
    {
    
        int x, y; // have to "predeclare"
    
        p.GetCoordinates(out x, out y);
    
        WriteLine($"({x}, {y})");
    
    }

    В C# 7.0 мы добавили out переменные, что позволяет объявлять переменную прямо в точке, где она передается как out аргумент:

    public void PrintCoordinates(Point p)
    
    {
    
        p.GetCoordinates(out int x, out int y);
    
        WriteLine($"({x}, {y})");
    
    }

     Обратите внимание, что переменные находятся в области видимости в окружающем блоке, поэтому последующая строка может их использовать. Многие виды утверждений не устанавливают свою собственную область действия, поэтому out переменные, объявленные в них, часто вводятся в область видимости.

    Поскольку out переменные объявляются непосредственно в качестве аргументов для out параметров, компилятор может обычно указывать, каков должен быть их тип (если только не существует конфликтующих перегрузок), поэтому вместо типа можно использовать ключевое слово var:

    p.GetCoordinates(out var x, out var y);

     Общим использованием out параметров является шаблон Try..., где логическое возвращаемое значение указывает на успех, а out параметры переносят полученные результаты:

    public void PrintStars(string s)
    
    {
    
        if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); }
    
        else { WriteLine("Cloudy - no stars tonight!"); }
    
    }

     Мы также допускаем «сбрасывание» в качестве out параметров в виде «_», что позволит вам проигнорировать параметры, которые вам не нужны:

    p.GetCoordinates(out var x, out _); // I only care about x

    Соответствие с шаблоном

    В C# 7.0 вводится понятие шаблонов, которые являются синтаксическими элементами, позволяющими проверить соответствие значения определенной «форме» и извлечь информацию из значения, если такое соответствие имеется.

    Примеры шаблонов в C# 7.0:

    • Константные шаблоны c (где c – константное выражение в C#), которые проверяют, равняется ли переменная этой константе.

    • Шаблоны типа T x (где T – тип и x – идентификатор), которые проверяют, имеет ли переменная тип T, и если да, то извлекают значение в новую переменную x типа T.

    • Var шаблоны var x (где x – идентификатор), которые всегда совпадают и просто помещают значение ввода в новую переменную x с тем же типом.

    Это только начало; шаблоны являются новым типом элемента языка C#, и в будущем мы обязательно добавим новые шаблоны в C#.

    В C# 7.0 мы улучшаем две существующие языковые конструкции с шаблонами:

    is теперь может использоваться не только с типом, но и с шаблоном;

    case в операторе switch теперь может использовать шаблоны, а не только константы.

    В будущих версиях C#, вероятно, мы добавим больше мест, где можно использовать шаблоны.

    Шаблоны с is

    Рассмотрим пример использования is с константным шаблоном и шаблоном типа:

    public void PrintStars(object o)
    
    {
    
        if (o is null) return;     // constant pattern "null"
    
        if (!(o is int i)) return; // type pattern "int i"
    
        WriteLine(new string('*', i));
    
    }

    Как видно из примера, переменные шаблона, представленные шаблоном, аналогичны out переменным, описанным ранее, поэтому могут быть объявлены в середине выражения и использоваться в ближайшей окружающей области. Также как out переменные, переменные шаблона изменяемы. Мы часто ссылаемся на out переменные и переменные шаблона совместно как «переменные выражения».

    Шаблоны и Try-методы часто используются вместе:

    if (o is int i || (o is string s && int.TryParse(s, out i)) { /* use i */ }

    Шаблоны с выражениями switch

    Мы обобщаем варианты использования switch:

    • Вы можете использовать любой тип (не только простые типы).

    • Шаблоны могут использоваться в выражениях case.

    • Вы можете добавлять дополнительные условия к выражениям case.

    Вот простой пример:

    switch(shape)
    
    {
    
        case Circle c:
    
            WriteLine($"circle with radius {c.Radius}");
    
            break;
    
        case Rectangle s when (s.Length == s.Height):
    
            WriteLine($"{s.Length} x {s.Height} square");
    
            break;
    
        case Rectangle r:
    
            WriteLine($"{r.Length} x {r.Height} rectangle");
    
            break;
    
        default:
    
            WriteLine("");
    
            break;
    
        case null:
    
            throw new ArgumentNullException(nameof(shape));
    
    }

    Существует несколько особенностей, которые следует отметить в этом новом расширенном выражении switch:

    • Порядок выражений case теперь имеет значение: как и в случае с выражениями catch, у выражений case выбирается первое по порядку выражение, удовлетворяющее условию. Поэтому важно, чтобы условие квадрата было перед условием прямоугольника. Кроме того, как и в случае с выражениями catch, компилятор поможет вам пометить явные недостижимые условия. До этого вы не могли определить порядок выполнения, так что это не является нарушением существующего поведения.

    • Условие по умолчанию (default) всегда вычисляется последним: несмотря на то, что после него идет условие null, условие default будет проверено после него. Это сделано для совместимости с существующей семантикой. Однако, как правило, вы помещаете условие default  в конце.

    • Условие null в конце достижимо, потому что шаблоны типов следуют примеру текущего is  и не срабатывают для null. Это гарантирует, что null значения не будут случайно сопоставлены с первым шаблоном типа; вы должны явно указать, как им управлять (или оставить логику для условия default).

    Переменные шаблона, объявленные ключевым словом case..., находятся в области видимости только в соответствующем разделе switch.

    Кортежи

    Обычно хочется вернуть несколько значений из метода. Все доступные варианты в существующих версиях C# являются менее оптимальными:

    Out параметры: использование является неэффективным (даже при использовании рассмотренных нововведений) и они не работают с асинхронными методами.

    System.Tuple <...>: выглядит многословным для использования и требует выделения кортежного объекта.

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

    Анонимные типы, возвращаемые через тип возврата dynamic: потери в производительности и отсутствие проверки статического типа.

    Для упрощения этой задачи в C# 7.0 были добавлены кортежи и литералы кортежей:

    (string, string, string) LookupName(long id) // tuple return type
    
    {
    
        ... // retrieve first, middle and last from data storage
    
        return (first, middle, last); // tuple literal
    
    }

     Теперь метод эффективно возвращает три строки, объединенные как элементы кортежа.

    Вызывающий код метода получит кортеж и может индивидуально иметь доступ к элементам:

    var names = LookupName(id);
    
    WriteLine($"found {names.Item1} {names.Item3}.");
     

    Имена полей Item1 и т. д. являются именами по умолчанию для элементов кортежа и могут использоваться всегда. Но они не очень наглядны, поэтому вы можете добавить лучшие имена:

    (string first, string middle, string last) LookupName(long id) // tuple elements have names
     

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

    var names = LookupName(id);
    
    WriteLine($"found {names.first} {names.last}.");

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

    return (first: first, middle: middle, last: last); // named tuple elements in a literal

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

    Кортежи – это типы значений, а их элементы – общедоступные изменяемые поля. Они имеют значение равенства, а это значит, что два кортежа являются равными (и имеют одинаковый хэш-код), если все их элементы попарно равны (и имеют одинаковый хэш-код).

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

    Кортежи полагаются на базовые структурные типы, которые называются ValueTuple <...>. Если вы выявите модель, что еще не включает эти типы, вы можете вместо этого выбрать их с помощью NuGet:

    • Щелкните правой кнопкой мыши проект в обозревателе решений и выберите «Manage NuGet Packages…».

    • Выберите вкладку «Browse» и выберите «nuget.org» в качестве «Package source».

    • Найдите «System.ValueTuple» и установите его.

    Распаковка кортежей

    Еще один способ использования кортежа – это его распаковка. Объявление распаковки является синтаксисом для разделения кортежа (или другого значения) на его части и назначения этих частей по отдельности новым переменным:

    (string first, string middle, string last) = LookupName(id1); // deconstructing declaration
    
    WriteLine($"found {first} {last}.");

    В объявлении распаковки можно использовать ключевое слово var для отдельных переменных:

    (var first, var middle, var last) = LookupName(id1); // var inside

    Или даже поместить var перед скобками как аббревиатуру:

    var (first, middle, last) = LookupName(id1); // var outside

    Вы также можете распаковать в уже существующие переменные с помощью присвоения распаковки:

    (first, middle, last) = LookupName(id2); // deconstructing assignment

    Распаковка выполняется не только для кортежей. Любой тип может быть распакован, если у него есть метод распаковки (образец или расширение):

    public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }

    Out параметры соответствуют значениям, которые будут присвоены в результате распаковки.

    (Почему используются out параметры, а не кортежи? Чтобы можно было иметь несколько перегрузок метода с разным количеством параметров).

    class Point
    
    {
    
        public int X { get; }
    
        public int Y { get; }
    
        public Point(int x, int y) { X = x; Y = y; }
    
        public void Deconstruct(out int x, out int y) { x = X; y = Y; }
    
    }
    
    (var myX, var myY) = GetPoint(); // calls Deconstruct(out myX, out myY);

    Это будет обычный шаблон для создания «симметричных» конструкторов и методов распаковки таким способом.

    Так же, как и для out переменных, мы разрешаем «сбрасывать» в распаковке параметры, которые вам не нужны:

    (var myX, _) = GetPoint(); // I only care about myX

    Локальные функции

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

    public int Fibonacci(int x)
    
    {
    
        if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x));
    
        return Fib(x).current;
    
        (int current, int previous) Fib(int i)
    
        {
    
            if (i == 0) return (1, 0);
    
            var (p, pp) = Fib(i - 1);
    
            return (p + pp, p);
    
        }
    
    }

    Параметры и локальные переменные из области видимости доступны для локальной функции так же, как и для лямбда-выражений.

    В качестве примера рассмотрим методы, реализованные как итераторы, что обычно нуждаются в неитераторном методе-оболочке для точной проверки аргументов в момент их вызова (так как сам итератор не запускается, пока не будет вызван MoveNext). Локальные функции идеально подходят для этого сценария:

    public IEnumerable Filter(IEnumerable source, Func filter)
    
    {
    
        if (source == null) throw new ArgumentNullException(nameof(source));
    
        if (filter == null) throw new ArgumentNullException(nameof(filter));
    
        return Iterator();
    
        IEnumerable Iterator()
    
        {
    
            foreach (var element in source) 
    
            {
    
                if (filter(element)) { yield return element; }
    
            }
    
        }
    
    }

    Если бы Iterator был приватным методом рядом с Filter, то мог быть доступен для других членов в использовании напрямую (без проверки аргументов). Кроме того, необходимо было бы передавать все те же аргументы, что и Filter, вместо того, чтобы иметь их только в области видимости.

    Улучшения литералов

    В C# 7.0 появилась возможность добавлять «_» в качестве разделителя в числовые литералы:

    var d = 123_456;
    
    var x = 0xAB_CD_EF;

     Вы можете поместить разделитель в любом месте между цифрами, чтобы улучшить читабельность. Они не влияют на значение.

    Кроме того, C# 7.0 представляет бинарные литералы, так что вы можете указывать битовые шаблоны непосредственно вместо того, чтобы знать шестнадцатеричную систему наизусть.

    var b = 0b1010_1011_1100_1101_1110_1111;

     Локальные переменные и возвращаемые значения по ссылке

    Теперь можно не только передать параметры в метод по ссылке в  С# (с помощью ключевого слова ref), но и возвратить данные из метода по ссылке, а также сохранить в локальной переменной тоже по ссылке.

    public ref int Find(int number, int[] numbers)
    
    {
    
        for (int i = 0; i < numbers.Length; i++)
    
        {
    
            if (numbers[i] == number) 
    
            {
    
                return ref numbers[i]; // return the storage location, not the value
    
            }
    
        }
    
        throw new IndexOutOfRangeException($"{nameof(number)} not found");
    
    }
    
    int[] array = { 1, 15, -39, 0, 7, 14, -12 };
    
    ref int place = ref Find(7, array); // aliases 7's place in the array
    
    place = 9; // replaces 7 with 9 in the array
    
    WriteLine(array[4]); // prints 9

    Очень удобно передавать ссылки на определенные места в больших структурах данных. Например, в игре информация содержится в большом заранее выделенном массиве структур (во избежание пауз на сбор мусора). Теперь методы могут вернуть ссылку непосредственно на одну из таких структур, с помощью которой вызывающий код может читать и изменять эту структуру.

    Существуют некоторые ограничения для обеспечения безопасности:

    • Можно возвращать только ссылки, которые возвращать безопасно: ссылки, переданные в метод и ссылки на поля объектов.

    • Локальные переменные инициализируются определенной ячейкой памяти и в будущем не меняются.

    Обобщенные типы асинхронных возвратов

    До сегодняшнего дня асинхронные методы могли возвращать только void, Task или Task. В C# 7.0 позволяется создавать типы, которые также могут быть возвращены асинхронным методом.

    Например, можно создать структуру ValueTask, которая поможет избежать создания объекта Task в случае, когда результат асинхронной операции уже доступен в ожидаемое время. Для многих асинхронных сценариев, например, где используется буферизация, такой подход может значительно уменьшить число выделений памяти и таким образом значительно повысить производительность.

    Конечно, можно придумать и другие ситуации, в которых task-подобные объекты будут полезны. Правильное создание таких типов не будет простой задачей, поэтому мы не ожидаем, что большое количество разработчиков будут создавать их. Однако мы полагаем, что они будут появляться в различных моделях и прикладных интерфейсах, и вызывающий код сможет просто использовать await, как сейчас для Task.

    Больше членов в виде выражений

    Методы и свойства в виде выражений используются в C# 6.0, но не все типы членов можно было так объявлять. В C# 7.0 к списку членов в виде выражений добавилась поддержка аксессоров, конструкторов и финализаторов:

    class Person
    
    {
    
        private static ConcurrentDictionary names = new ConcurrentDictionary();
    
        private int id = GetId();
    
     
    
        public Person(string name) => names.TryAdd(id, name); // constructors
    
        ~Person() => names.TryRemove(id, out _);              // finalizers
    
        public string Name
    
        {
    
            get => names[id];                                 // getters
    
            set => names[id] = value;                         // setters
    
        }
    
    }

    Это пример функции, которая была предоставлена сообществом, а не командой компилятора Microsoft C#. Ура, открытый код!

    Throw выражения

    Выбросить исключение в середине выражения очень легко: достаточно вызвать метод, который это сделает! Но в C# 7 теперь можно использовать throw как часть выражения в определенном месте:

    class Person
    
    {
    
        public string Name { get; }
    
        public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));
    
        public string GetFirstName()
    
        {
    
            var parts = Name.Split(" ");
    
            return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
    
        }
    
        public string GetLastName() => throw new NotImplementedException();
    
    }

    Источник

    КОМЕНТАРІ ТА ОБГОВОРЕННЯ
    advertisement advertisement

    Купуй передплатуз доступом до всіх курсів та сервісів

    Бібліотека сучасних IT знань у зручному форматі

    Вибирай свій варіант підписки залежно від завдань, що стоять перед тобою. Але якщо потрібно пройти повне навчання з нуля до рівня фахівця, краще вибирати Базовий або Преміум. А для того, щоб вивчити 2-3 нові технології, або повторити знання, готуючись до співбесіди, підійде Пакет Стартовий.

    Стартовий
    • Усі відеокурси на 3 місяці
    • Тестування з 10 курсів
    • Перевірка 5 домашніх завдань
    • Консультація з тренером 30 хв
    59.99 $
    Придбати
    Весняний
    • Усі відеокурси на 15 місяців
    • Тестування з 24 курсів
    • Перевірка 20 домашніх завдань
    • Консультація з тренером 120 хв
    90.00 $
    219.99 $
    Придбати
    Акція
    Преміум
    • Усі відеокурси на 12 місяців
    • Тестування з 24 курсів
    • Перевірка 20 домашніх завдань
    • Консультація з тренером 120 хв
    169.99 $
    Придбати
    Notification success