Дмитpий Hecтepук

Блог о программировании — C#, F#, C++, архитектура, и многое другое

Что нового в C# 7

2 комментария

Ну что, всех можно поздравить с релизом VS2017? Для начала, я пожалуй признаюсь что я давно уже (время наверное на месяцы пошло) использую Rider (и даже запилил по нему видеокурс), но релиз VS2017 — это не только релиз слегка медленной, 32-битной Windows-only IDE которая даже не умеет правильно рисовать лигатуры. Это еще и релиз новых версий компиляторов — именно того, что Microsoft нам обещали делать out-of-band, но обещание оказалось наглой ложью, как для C++ так и для C#.

Что же, давайте посмотрим что там в новом сишарпике…

Локальные методы

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

public static Tuple<double,double> Solve(double a, double b, double c)
{
  double CalculateDiscriminant()
  {
    return b * b - 4 * a * c;
  }
  var disc = CalculateDiscriminant();
  return Tuple.Create(
    (-b + Math.Sqrt(disc)) / (2 * a),
    (-b - Math.Sqrt(disc)) / (2 * a)
  );
}

Что мы можем заметить в примере выше? Правильно, вложенная функция умеет захватывать окружение, в т.ч. аргументы внешней функции. И конечно же дать функции CalculateDiscriminant аргументы a,b,c не получилось бы, т.к. эти имена уже заняты.

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

Касательно полезности фичи: мне норм. Этот подход позволяет плодить меньше членов на уровне класса, а баловаться с лямбдами напряжно т.к. для любой лямбды нужно определить тип Func<int,Foo,string> и это порой бывает утомительно — хотя в некоторых случаях Решарпер помогает. Вообще было бы лучше если бы нам позволяли еще делать локальные статические переменные прямо в методах, как это позволяет делать C++.

Out-переменные

Обычно как было: декларация переменной это statement. Все, теперь это по сути expression т.к.:

string s;
if (DateTime.TryParse(s, out DateTime dt))
{
  // используем dt
}

Это чисто «сахарная» фича, и резонным будет вопрос — а что если DateTime не пропарсится а мы все равно попытаемся ей воспользоваться? Ну, тут все просто — у этой штуки будет дефолтное значение. Но зато заметьте, dt имеет тот же scope, что и строка s.

Кортежи

Кортежи уже были в C#, но вставлены они были криво: все эти Item1, Item2 не добавляли к удобству пользования, и кому-то могло показаться что рано или поздно нам вообще подсунут variadic templates. Но разрабы поймали себя и все-таки сделали что-то годное:

public static (double x1, double x2) Solve(double a, double b, double c)
{
  var disc = CalculateDiscriminant();
  double CalculateDiscriminant()
  {
    return b * b - 4 * a * c;
  }
  return (
    (-b + Math.Sqrt(disc)) / (2 * a), 
    (-b - Math.Sqrt(disc)) / (2 * a)
  );
}

И вызвать можно это вот так:

var (x1, x2) = QuadraticEquationSolver.Solve(1, 10, 16);
Console.WriteLine(x1 + ", " + x2);

Ну а если декларировать кортежи на месте, то правила те же самые, и есть варианты — давать элементам кортежа имена или нет:

var names = (first: "Dmitri", last: "Nesteruk");
Console.WriteLine(names.first);

Как видите, кортеж определяется как тип через скобочки и список типов (т.е. (double,double) условно-эквивалентно ValueTuple<double,double>). Но ложка дегтя тут огромная: для всего этого нужен внешний пакет System.ValueTuple. Что?!?!?

Спокойно, очевидно что просто эту тему недопилили и ValueTuple<T, ...>, который должен был быть в .NET 4.6.2 BCL просто туда не попал. Что это за класс? Да просто еще одна реализация кортежа, но в этот раз вся ее суть закрыта синтаксическим сахаром вроде того что я привел выше. Если кому интересно что под капотом, вот сорцы.

Expression-bodied members

На момент C#6, некоторые вещи можно было писать как expression bodies, т.е например void Foo(x) => x+1; делал вполне себе валидный метод. Сейчас же, это счастье распространилось помимо методов и пропертей еще на конструкторы/деструкторы, геттеры и сеттеры:

class Person
{
  Person() => Names = new[]{"Nameless One"};
  public int FirstName
  {
    get => Names[0]; // ну вы поняли
  }
}

Вообще вся эта expression bodied тематика экономит хорошо так символов. Единственное что мне не нравится это каша вроде

class Person
{
  bool CanVote => Age <= 16; // без лигатур - коряво :(
}

но это просто один конкретный пример того как можно получить нечитаемый код.

Throw Expressions

Тут мне 2017 студия осмелилась подправить Решарперный код. Было что-то вроде этого:

class Foo
{
  Foo(Bar bar)
  {
    if (bar == null) throw new SomeException();
    this.bar = bar;
  }
}

но теперь оказывается можно писать вот так

class Foo
{
  Foo(Bar bar)
  {
    this.bar = bar ?? throw new SomeException();
  }
}

Интересный ход конем, однако. Хорошо ли бросать из места где ожидается конкретный return value? Ну, если компилятор может это безопасно обработать, почему бы и нет — главное потом не пытаться прочитать поле, которое мы так и недописали.

Pattern matching

Когда-то я писал про самопальный pattern matching, но тут сделали реальную реализацию.

Начнем с простого — теперь проверку на тип можно сделать прямо в if-е и сразу получить и проверку и приведенный тип:

if (shape is Rectangle rc)
{
  var area = rc.Width * rc.Height;
}

Ну и потом нам разрешили делать switch на этом месте:

Shape s = new Rectangle { width = 0, height = 0 };
switch (s)
{
  case Rectangle rc when (rc.width == rc.height):
    Console.WriteLine("It's a square!");
    break;
  case Circle c:
    break;
}

Заметьте ключевое слово when выше. Ничего не напоминает? Правильно, F#. Но конечно F# все еще лидирует в этом плане. Хотя бы потому, что в F# можно делать алгебраические типы данных, что очень полезно когда ты, например, что-нибудь структурированное парсишь, например MathML.

Ну хорошо, смотрите, хоть что-то сделали. Конечно, нельзя мэтчить на Rectangle(42, double h) — это откинули на будущее. И switch на кортежах тоже пока не работает. Грусть-печаль.

Ref returns

Как вернуть элемент массива by reference? Ну строго говоря, можно запинить весь массив и попытаться вернуть указатель в unsafe. Но теперь есть более гуманный способ — ref на чем угодно.

int[] numbers = new int[] { 1, 2, 3 };
ref int refToSecond = ref numbers[1];
refToSecond = 123;
Console.WriteLine(string.Join(",",numbers)); // 1, 123, 3

Хмм, ну это хорошо конечно, но со списком такое не пройдет

List<int> numbers = new List<int> { 1, 2, 3 };
ref int second = ref numbers[1]; // A property or indexer cannot be used⋮

Хмм, да, полезность сей затеи стремится к нулю. Нельзя например сослаться на букву в строке.

Все это — простой aliasing, то есть еще одно имя для той же самой переменной. Это не «ссылка на область памяти» т.к. это обычно требует слово fixed чтобы вменяемо работать в unsafe контексте, а у нас все тут safe.

Да, теперь можно и из метода ref на переданный ref возвращать, то есть возможно вот такое мракобесие:

static ref int Min(ref int x, ref int y) {
  if (x < y)
    return ref x;
  else
    return ref y;
}
⋮
int a = 1, b = 2;
 ref int c = ref Min(ref a, ref b);
c = 123; // a == 123!!!

Holy s~t, посмотрите сколько тут раз ref написано. Даже в вызове используется ref Min(...) потому что если ты опустишь этот ref это значит что тебе не нужен референс на значение, а нужно само значение.

int d = Min(ref a, ref b); // вполне валидно, d == 1

Проблема тут в том, что этот ref — это то же самое что референсы в C++, из-за которых теперь в уютном сишарпике можно писать треш вроде Min(ref a, ref b) += 123 (по сути делает a += 123), что абсолютно отвратительно для читания и понимания. К тому же, неопытные программисты тут же будут ломиться и возвращать адреса локальных переменных, что делать нельзя:

static ref int Foo()
{
  int x = 0;
  return ref x; // не скомпилируется - cannot return local by reference
}

Хотя помнится что году так в 2012, Липперт угрожал что они могут и этот сценарий поддержать.

Что еще тут?

Теперь можно делать подчеркивания в литералах: int x = 123_456;. Также появились бинарные литералы — теперь можно писать var literal = 1100_1011_____0101_1010_1001;. Подчеркиваний может быть сколько угодно.

Pattern matching — все самое вкусное не попало в C#7, ждем следующих версий. Должны быть всякие вот такие штуки поддержаны:

switch (foo)
{
  case 123: ⋮
  case MyEnum.Stuff: ⋮
  case string s: ⋮
  case Point(int x, 321) where x > 0: ⋮
  case Point(12, 23) : ⋮
  default: ⋮
}

Record types — не вошли, будут в C#8 наверное.

Ну вообщем как-то так. А вы заметили насколько шустрее стал компилятор C#7? Серьезно, видимо опять что-то подкрутили с инкрементальностью. Кстати, в VS2015 сделали обалденные улучшения инкрементальности C++, а тут кажется улучшили C#. У меня «горячая» компиляция летает, хотя «холодная» все еще тормозит как и раньше.

Наверняка что-то еще из фич забыл, напишите в комментариях. ■

Реклама

Written by Dmitri

13 марта 2017 в 10:54

Опубликовано в C#

комментария 2

Subscribe to comments with RSS.

  1. А есть идеи почему в pattern matching’е используется ключевое слово `when` вместо привычного (как по смыслу так и по значению) `&&`? Т.е. почему не так:

    `case Rectangle rc && rc.width == rc.height:`

    T

    15 марта 2017 at 2:50

    • Там вроде были 2 варианта, where и when. where наверняка побоялись потому что это уже ключевое слово в LINQ, в то время как when используется в F# и как бы привычно. Насчет && — чисто теоретически можно было так сделать, но && обычно подразумевает что все операнды типа bool, а в нашем случае это совсем не так. Так что это было бы сложно парсить, т.е. пришлось бы менять семантику. А этого никто не хочет. Вот представьте, что если бы вы написали case IAmConvertibleToBool z && ... — если у z есть implicit operator bool, уже непонятно, тестируем ли мы только на тип или на тип и его конвертируемость в true. Такие неоднозначности никто не любит.

      Dmitri

      15 марта 2017 at 11:34


Оставить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: