Нововведения в С# 7.0 - Блог ITVDN
ITVDN: курсы программирования
Видеокурсы по
программированию

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

    Подписка

    Выбери свою 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 $
      Оформить подписку
      Базовый
      • Все видеокурсы на 6 месяцев
      • Тестирование по 16 курсам
      • Проверка 10 домашних заданий
      • Консультация с тренером 60 мин
      54.00 $
      89.99 $
      Оформить подписку
      Акция
      Премиум
      • Все видеокурсы на 1 год
      • Тестирование по 24 курсам
      • Проверка 20 домашних заданий
      • Консультация с тренером 120 мин
      85.00 $
      169.99 $
      Оформить подписку
      Акция
      Notification success
      Мы используем cookie-файлы, чтобы сделать взаимодействие с нашими веб-сайтами и услугами простым и значимым.