Исследование кортежей в C# 7

Автор: admin от 23-12-2017, 20:25, посмотрело: 295

Типы System.Tuple были введены в .NET 4.0 с двумя существенными недостатками:




  • Типы кортежей являются классами;

  • Не существует языковой поддержки для их создания/деконструкции (deconstruction).



  • Чтобы решить эти проблемы, в C# 7 представлена новая возможность языка, а также новое семейство типов (*).



    Сегодня, если вам нужно склеить два значения, чтобы вернуть их из функции или поместить два значения в хэш-набор, вы можете использовать типы System.ValueTuple и создать их с помощью удобного синтаксиса:

    System.ValueTuple.




  • Синтаксис объявления Tuple похож на объявление параметра функции: (Type1 name1, Type2 name2).

  • Синтаксис создания экземпляров Tuple похож на передачу аргументов: (value1, optionalName: value2).

  • Два кортежа с одинаковыми типами элементов, но с разными именами, совместимы (**): (int a, int b) = (1, 2).

  • Кортежи имеют семантику значений:

    (1,2) .Equals ((a: 1, b: 2)) и (1,2) .GetHashCode () == (1,2) .GetHashCode () являются истинными.

  • Кортежи не поддерживают == и !=. В github обсуждается эта возможность: «Поддержка == и! = Для типов кортежей».

  • Кортежи могут быть «деконструированы», но только в «объявление переменной», но не в «out var» или в блок case:

    var (x, y) = (1,2) — OK, (var x, int y) = ( 1,2) — OK,

    dictionary.TryGetValue (key, out var (x, y)) — не OK, case var (x, y): break; — не ОК.

  • Кортежи изменяются: (int a, int b) x (1,2); x.a++;.

  • Элементы кортежа можно получить по имени (если указано при объявлении) или через общие имена, такие как Item1, Item2 и т. Д.



  • (**) Мы скоро увидим, что это не всегда так.



    Именованные элементы кортежа



    Отсутствие пользовательских имен делает типы System.Tuple не очень полезными. Я могу использовать System.Tuple как часть реализации небольшого метода, но если мне нужно передать его экземпляр, я предпочитаю именованный тип с описательными именами свойств. Кортежи в C# 7 довольно элегантно решают эту проблему: вы можете указать имена для элементов кортежа и, в отличие от анонимных классов, эти имена доступны даже в разных сборок.



    Компилятор C# генерирует специальный атрибут TupleElementNamesAttribute (***) для каждого типа кортежа, используемого в сигнатуре метода:



    (***) Атрибут TupleElementNamesAttribute является специальным и не может использоваться непосредственно в коде пользователя. Компилятор выдает ошибку, если вы попытаетесь его использовать.



    public (int a, int b) Foo1((int c, int d) a) => a;
     
    [return: TupleElementNames(new[] { "a", "b" })]
    public ValueTuple<int, int> Foo(
        [TupleElementNames(new[] { "c", "d" })] ValueTuple<int, int> a)
    {
        return a;
    }
    


    Данный атрибут помогает IDE и компилятору «видеть» имена элементов и предупреждать, если они используются неправильно:



    // Ok: tuple literal can skip element names
    (int x, int y) tpl = (1, 2);
     
    // Warning: The tuple element 'a' is ignored because a different name
    // or no name is specified by the target type '(int x, int y)'.
    tpl = (a:1, b:2);
     
    // Ok: tuple deconstruction ignore element names
    var (a, b) = tpl;
     
    // x: 2, y: 1. Tuple names are ignored
    var (y, x) = tpl;
    


    У компилятора более высокие требования к унаследованным членам:



    public abstract class Base
    {
        public abstract (int a, int b) Foo();
        public abstract (int, int) Bar();
    }
     
    public class Derived : Base
    {
        // Error: Cannot change tuple element names when overriding method
        public override (int c, int d) Foo() => (1, 2);
        // Error: Cannot change tuple element names when overriding method
        public override (int a, int b) Bar() => (1, 2);
    }
    


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



    Вывод имени элемента



    C # 7.1 появилось одно дополнительное усовершенствование: вывод имени элемента кортежа аналогичен тому, что C# делает для анонимных типов.



    public void NameInference(int x, int y)
    {
        // (int x, int y)
        var tpl = (x, y);
     
        var a = new {X = x, Y = y};
     
        // (int X, int Y)
        var tpl2 = (a.X, a.Y);
    }
    


    Семантика значений и изменяемость.



    Кортежи являются изменяемыми значимыми типами. Мы знаем, что изменяемые значимые типы считаются вредными. Вот небольшой пример их злой природы:



    var x = new { Items = new List<int> { 1, 2, 3 }.GetEnumerator() };
    while (x.Items.MoveNext())
    {
        Console.WriteLine(x.Items.Current);
    }
    


    Если вы запустите этот код, вы получите… бесконечный цикл. Список List .Enumerator — это изменяемый значимый типа, а Items свойство. Это означает, что x.Items возвращает копию исходного итератора на каждой итерации цикла, вызывая бесконечный цикл.



    Но изменяемые значимые типы опасны только тогда, когда данные смешиваются с поведением: Enumerator содержит состояние (текущий элемент) и имеет поведение (возможность продвижения итератора путем вызова метода MoveNext). Эта комбинация может вызывать проблемы, потому что легко вызвать метод на копии, вместо исходного экземпляра, что приводит к эффекту no-op (No Operation). Вот набор примеров, которые могут вызвать неочевидное поведение из-за скрытой копии типа значения: gist.



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



    var tpl = (x: 1, y: 2);
    var hs = new HashSet<(int x, int y)>();
    hs.Add(tpl);
     
    tpl.x++;
    Console.WriteLine(hs.Contains(tpl)); // false
    


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



    Деконструкция



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



    public static class VersionDeconstrucion
    {
        public static void Deconstruct(this Version v, out int major, out int minor, out int build, out int revision)
        {
            major = v.Major;
            minor = v.Minor;
            build = v.Build;
            revision = v.Revision;
        }
    }
     
    
    var version = Version.Parse("1.2.3.4");
    var (major, minor, build, _) = version;
     
    // Prints: 1.2.3
    Console.WriteLine($"{major}.{minor}.{build}");
    


    Разбор (деконструкция) кортежа использует подход «утиной типизации»: если компилятор может найти метод Deconstruct для данного типа – экземплярный метод или метод расширения — тип является разбираемым.



    Алиасы кортежей



    После того, как вы начнете использовать кортежи, вы быстро поймете, что хотите «повторно использовать» тип кортежа с именованными элементами в нескольких местах исходного кода. Но с этим не все так просто.



    Во-первых, C # не поддерживает глобальные псевдонимы для заданного типа. Вы можете использовать 'using' alias директиву, но она создает псевдоним, видимый в одном файле.



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



    // You can't do this: compilation error
    using Point = (int x, int y);
     
    // But you *can* do this
    using SetOfPoints = System.Collections.Generic.HashSet<(int x, int y)>;
    


    Сейчас на github в теме «Типы Tuple при использовании директив» идет обсуждение этой проблемы. Поэтому, если вы обнаружите, что используете один тип кортежа в нескольких местах, у вас есть два варианта: либо копировать во типы по всей кодовой базе либо создать именованный тип.



    Какое правило именования для элементов я должен использовать?



    Pascal case, например ElementName, или camel case, например elementName? С одной стороны, элементы кортежей должны следовать правилу именования для публичных членов (т.е. PascalCase), но, с другой стороны, кортежи — это просто хранилище для переменных, а переменные именуются с camelСase.



    Вы можете использовать следующий подход:




    • PascalCase, если кортеж используется в качестве аргумента или возвращаемого типа метода;

    • camelCase, если кортеж создается локально в функции.



    Но я предпочитаю использовать camelCase все время.



    Вывод



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



    Я даже использую их, чтобы избежать аллокации замыкания с помощью таких методов, как ConcurrentDictionary.TryGetOrAdd, который теперь принимает дополнительный аргумент. И во многих случаях, состояние также является кортежем.



    Эти фичи очень полезны, но я действительно хочу увидеть несколько улучшений:




  • Глобальные псевдонимы: возможность «называть» кортеж и использовать их во всей сборке (****).

  • Разбор кортежа в сопоставлении с образцом: в out var и в case var .

  • Использование оператор == для сравнения равенства.



  • (****) Я знаю, что эта функция спорная, но я думаю, что это будет очень полезно. Мы можем дождаться типов Record, но я не уверен, будут ли записи значимыми типами или ссылочными типами.

    Источник: Хабрахабр

    Теги: 7, net

    Категория: Программирование

    Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
    Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

    Добавление комментария

    Имя:*
    E-Mail:
    Комментарий:
    Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
    Введите два слова, показанных на изображении: *