- PVSM.RU - https://www.pvsm.ru -
С релизом Visual Studio “15” Preview 4 [1] многие новые фичи C# 7 можно попробовать самому. Основные новшества C# 7 призваны облегчить работу с данными, упростить код и улучшить производительность. От себя скажу, что C# движется в сторону функционального языка, добавляя такие вещи, как кортежи и сопоставления с шаблоном (pattern matching). Не все из новой функциональности работает так, как задумано, в Preview 4, в этих случаях будет указано, что именно вы можете пока использовать и как это будет работать в будущем. Что ж, приступим.
Сейчас использовать out переменные не так легко, как нам хотелось бы. Перед тем как вызвать метод с out аргументами, необходимо объявить переменные, которые будут переданы в этот метод. Так как обычно значения этим переменным во время объявления не присваиваются (что логично — они все равно будут перезаписаны методом), ключевое слово var использовать не получится. Надо объявлять переменные с указанием их типа:
public void PrintCoordinates(Point p)
{
int x, y; // нужно объявить переменные
p.GetCoordinates(out x, out y);
WriteLine($"({x}, {y})");
}
В C# 7 добавлены out переменные, которые позволяют объявить переменные сразу в вызове метода:
public void PrintCoordinates(Point p)
{
p.GetCoordinates(out int x, out int y);
WriteLine($"({x}, {y})");
}
Область видимости для таких переменных является внешний блок, именно поэтому можно их использовать в следующем за вызовом метода выражении.
Внимание: В Preview 4 действуют более строгие ограничения на область видимости: 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("Облачно - звезд нет!"); }
}
Внимание: В данном примере i используется только внутри блока if, в котором определена, поэтому в Preview 4 этот пример тоже работает.
Одним из возможных улучшений (которое необязательно попадет в C# 7), может стать использование подстановочных символов ( * ) вместо тех out параметров, которые не будут использоваться далее. Например:
p.GetCoordinates(out int x, out *); // Нам нужен только x
В седьмой версии C# появляется понятие шаблона (pattern), который в общем случае представляет собой синтаксическую конструкцию, позволяющую проверить соответствие переменной определенному шаблону и извлечь из нее информацию, если такое соответствие имеется.
В C# 7 есть следующие шаблоны:
Это всего лишь начало, и в будущем мы обязательно добавим новые шаблоны в C#.
Для поддержки шаблонов были изменены 2 уже существующих языковых конструкции:
В будущем мы добавим еще возможности использования шаблонов.
Рассмотрим простой пример, в котором используются и константный шаблон, и шаблон типа.
public void PrintStars(object o)
{
if (o is null) return; // константный шаблон "null"
if (!(o is int i)) return; // шаблон типа "int i"
WriteLine(new string('*', i));
}
Как видно из примера, переменные шаблона (которые были объявлены в шаблоне), имеют ту же область видимости, что и out переменные, поэтому могут использоваться внутри внешнего блока видимости.
Внимание: В Preview 4 для переменных шаблона так же, как и для out переменных, действуют более строгие правила видимости, поэтому пример заработает только в следующих релизах.
Шаблоны и Try-методы могут использоваться вместе:
if (o is int i || (o is string s && int.TryParse(s, out i)) { /* можно использовать i типа int */ }
Варианты использования switch были расширены, теперь можно:
Теперь рассмотрим пример:
switch(shape)
{
case Circle c:
WriteLine($"круг с радиусом {c.Radius}");
break;
case Rectangle s when (s.Length == s.Height):
WriteLine($"{s.Length} x {s.Height} квадрат");
break;
case Rectangle r:
WriteLine($"{r.Length} x {r.Height} прямоугольник");
break;
default:
WriteLine("<неизвестная фигура>");
break;
case null:
throw new ArgumentNullException(nameof(shape));
}
Отметим следующие особенности нового расширенного switch:
Областью видимости для переменных шаблона, объявленных в case, является выражение switch.
Иногда хочется вернуть несколько значений из метода. Ни один из доступных на сегодняшний момент способов не выглядит оптимальным:
Для упрощения этой задачи в C# 7 были добавлены кортежи и литералы кортежей:
(string, string, string) LookupName(long id) // возвращаемый тип - кортеж
{
... // инициализируем данные
return (first, middle, last); // литерал кортежаl
}
Теперь метод возвращает 3 строки, объединенных в кортеж. Вызывающий код может их использовать следующим образом:
var names = LookupName(id);
WriteLine($"найдены {names.Item1} {names.Item3}.");
Имена полей Item1, Item2, ... являются именами по умолчанию для каждого кортежа, однако, есть возможность дать данным, объединенным в кортеж, имена получше:
(string first, string middle, string last) LookupName(long id) // элементы кортежа теперь имеют собственные имена
Теперь к элементам кортежа можно обратиться так:
var names = LookupName(id);
WriteLine($"найдены {names.first} {names.last}.");
Также имена элементов можно указать сразу в литерале кортежа:
return (first: first, middle: middle, last: last); //указываем имена элементов в литерале кортежа
Кортежи можно присваивать друг другу, если имена их элементов не совпадают: главное, чтобы сами элементы можно было присваивать друг другу. В будущем будут добавлены ограничения, в основном для литералов кортежей, которые будут сигнализировать о таких ошибках, как случайно поменянные местами имена элементов и т.д. В Preview 4 таких ограничений еще нет.
Кортежи представляют собой значимый тип, а их элементы — изменяемые открытые поля. Кортежи могут сравниваться на равенство: два кортежа равны (и имеют одинаковый хэш код), если все составляющие элементы равны друг с другом попарно (и имеют одинаковый хэш код). Такое поведение делает кортежи полезными не только для возвращения нескольких значений из метода. Например, если вам нужен словарь с составным ключом, используйте в качестве ключа кортеж. Если вам нужен список, где на каждой позиции должно быть несколько значений, также использует список кортежей. (От переводчика: не надо воспринимать это как руководство к использованию кортежей в 100% ситуациях, иногда простой класс с парой-тройкой свойств лучше выразит ваши намерения и будет легче поддерживаем в будущем).
Внимание: Кортежи в своей работе опираются на типы, которых еще нет в Preview 4, но их можно добавить в проект с помощью NuGet (не забудьте выбрать «Include prerelease» и указать «nuget.org» в качестве «Package source»), пакет называется System.ValueTuple.
Еще один способ работы с кортежем — его распаковка, которая заключается в присваивании его элементов новым переменным:
(string first, string middle, string last) = LookupName(id1); // deconstructing declaration
WriteLine($"найдены {first} {last}.");
Также можно использовать ключевое слово var вместо типа для каждой переменной:
(var first, var middle, var last) = LookupName(id1); // var внутри
Или даже поместить var перед скобками:
var (first, middle, last) = LookupName(id1); // var снаружи
Также распаковать кортеж можно в уже объявленные переменные:
(first, middle, last) = LookupName(id2); //переменные уже объявлены
Распаковать можно не только кортеж, любой тип может быть распакован. Для этого он должен иметь метод следующего вида:
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(); // вызывает Deconstruct(out myX, out myY);
Такой подход позволит создавать «симметричные» конструктор и метод распаковки.
Так же как и для out переменных мы планируем добавить подстановочные символы для игнорирования некоторых возвращаемых параметров.
(var myX, *) = GetPoint(); // нам нужен только myX
Внимание: До сих пор неизвестно, будут ли добавлены подстановочные символы в C# 7.
Иногда вспомогательная функция имеет смысл только внутри одного метода, в котором вызывается. Теперь такую функцию можно объявить внутри метода:
public int Fibonacci(int x)
{
if (x < 0) throw new ArgumentException("Не надо негатива!", 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<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (filter == null) throw new ArgumentNullException(nameof(filter));
return Iterator();
IEnumerable<T> Iterator()
{
foreach (var element in source)
{
if (filter(element)) { yield return element; }
}
}
}
Если бы метод Iterator был обычным приватным методом, то он мог быть вызван случайно, без проверки аргументов. Кроме того, ему потребовалось бы передавать те же самые аргументы, что и методу Filter.
Внимание: В Preview 4 локальные функции должны быть объявлены перед вызовом. В будущем это ограничение будет ослаблено: локальные функции можно будет вызвать после того, как всем локальным переменные, ими используемые, будут присвоены значения.
В C# 7 появилась возможность добавлять _ в качестве разделителя в числовые литералы:
var d = 123_456;
var x = 0xAB_CD_EF;
Разделитель можно добавить в любом месте между цифрами, на значение он не влияет.
Также в C# 7 появились бинарные литералы:
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]; // возвращаем ссылку на место хранения, а не значение элемента массива
}
}
throw new IndexOutOfRangeException($"{nameof(number)} не найден");
}
int[] array = { 1, 15, -39, 0, 7, 14, -12 };
ref int place = ref Find(7, array); // ссылка на место, где находится 7 в массиве
place = 9; // заменяем 7 на 9
WriteLine(array[4]); // выведет 9
Теперь будет удобно передавать ссылки на определенные места в больших структурах данных. Например, в игре информация содержится в большом заранее выделенном массиве структур (чтобы избежать пауз на сбор мусора). Теперь методы могут вернуть ссылку на одну из таких структур, с помощью которой вызывающий код может читать и изменять эту структуру.
Для того, чтобы работать с ссылками было безопасно, введены следующие ограничения:
До сегодняшнего дня, async методы могли возвращать только void, Task or Task<T>. В C# 7 появилась возможность создания типов, которые также могут быть возвращены асинхронным методом. Например, можно создать структуру ValueTask<T>, которая поможет избежать создания объекта Task<T> в случае, когда результат асинхронной операции уже доступен. Для многих асинхронных сценариев, например, где используется буферизация, такой подход может значительно уменьшить число выделений памяти и таким образом повысить производительность.
Конечно, можно придумать и другие ситуации, в которых Task-подобные объекты будут полезны. Правильное создание таких типов не будет простой задачей, поэтому мы не ожидаем, что большое число разработчиков будут создавать их, однако мы думаем, что в различных фреймворках они будут полезны, и вызывающий код сможет просто использовать await, как сейчас для Task.
Внимание: В Preview 4 эти типы еще не доступны.
Методы и свойства в виде выражений (expression bodied members), появившиеся в C# 6, часто использовались, но не все типы членов класса можно было так объявлять. Теперь в C# 7 добавилась поддержка сеттеров, геттеров, конструкторов и деструкторов:
class Person
{
private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();
private int id = GetId();
public Person(string name) => names.TryAdd(id, name); // конструктор
~Person() => names.TryRemove(id, out *); // декструктор
public string Name
{
get => names[id]; // геттер
set => names[id] = value; // сеттер
}
}
Это пример новой функциональности, добавленной сообществом, а не командой разработки компилятора! Ура, опен сорс!
Внимание: В Preview 4 поддержка этих членов класса недоступна.
Выбросить исключение в середине выражения не так уж сложно: достаточно вызвать метод, который это сделает. Но в C# 7 теперь можно использовать throw как часть выражения:
class Person
{
public string Name { get; }
public Person(string name) => Name = name ?? throw new ArgumentNullException(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();
}
Внимание: В Preview 4 такие выражения еще не доступны.
Хоть до релиза C# 7 еще и далеко, уже сейчас можно поиграться с большинством новых фич и понять, куда C# и .Net в целом движутся (как по мне, так C# берет часть фишек из функциональных языков, и зачастую это делает код более читаемым и менее многословным. Но везде нужно знать меру, конечно).
Автор: JustStas
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/192911
Ссылки в тексте:
[1] Visual Studio “15” Preview 4: https://blogs.msdn.microsoft.com/visualstudio/2016/08/22/visual-studio-15-preview-4/
[2] Источник: https://habrahabr.ru/post/311112/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.