Страницы

Поиск по вопросам

понедельник, 1 октября 2018 г.

Объясните смысл новшеств C# 7

В новой версии превью Visual Studio "15" появилась вот такая конструкция, которая возвращает ссылку на объект:
static void Main(string[] args) { int[] numbers = { 0b1, 0b10, 0b100, 0b1000, 0b1_000, 0b10_0000 }; ref int r = ref Find(numbers, x => x > 4); WriteLine($"{r} == {numbers[4]}"); r = 0; WriteLine($"{r} == {numbers[4]}"); }
static ref Find(int[] list, Func pred) { int i; for (i = 0; !pred(list[i]); i++) ; return ref list[i]; }
В чем смысл этого нововведения? Разве если мы возвращаем объект из метода, то мы его не возвращаем неявно ссылку на него?
Так же появились локальные функции. В чем их юзабельность, разве без них обойтись нельзя? На мой взгляд это понизит качество кода, так как со стороны функция будет похожа на класс с методами.
class Program { int[] numbers = { 0b1, 0b10, 0b100, 0b1000, 0b1_0000, 0b10_0000 };
(int, int) Tally(IEnumerable list) {
} }


Ответ

По первой части, это интересно для типов-значений.
С ссылочными типами нет особой разницы, работаете вы с объектом по оригиналу ссылки или по её копии. Но с типами-значениями, такими как int, вы получаете копию значения. Для возвращаемого ref вы можете работать таки с оригиналом.
Таким образом, вы уменьшаете объём копирования структур (которое может быть проблемой в высокопроизводительном коде).
Кроме того, вы сможете писать код наподобие «найти точку с наибольшим X и увеличить у неё Y», потому что судя по всему станут возможны функции наподобие
ref Point MaxBy(Point[] points, Func selector) { ref Point result = ref points[0]; for (int i = 1; i < points.Length; i++) if (selector(result) < selector(point[i])) result = ref point[i]; return result; }
ref Point rightmost = MaxBy(points, p => p.X); rightmost.Y += 1;

По поводу локальных функций, мне кажется, часто, наоборот, приватные функции классов используются как костыль на отсутствие локальных функций. Часто в приватную функцию выносится хелпер из одной функции, не имеющий значения внутри класса. Локальная функция — более правильный путь для таких функций.
Дополнительно, локальные функции «видят» локальные переменные в охватывающей функции, а значит, вам не придётся передавать в них кучу вспомогательных аргументов.

Ещё один юзкейс для локальных функций — итераторы и async-функции. Смотрите. Если у вас есть код
IEnumerable GetOdd(IEnumerable s) { if (s == null) throw new ArgumentNullException(); foreach (var v in s) if (v % 2 != 0) yield return v; }
— то проверка будет выполнена, и исключение брошено лишь после начала перечисления. То есть код
IEnumerable odds; try { odds = GetOdd(seq); } catch (ArgumentException) { return false; } foreach (var v in odds) Console.WriteLine(v);
не поймает исключение. То есть, пользователю придётся знать, будет ли исключение брошено во время вызова или во время перечисления.
С локальными функциями вы можете написать так:
IEnumerable GetOdd(IEnumerable s) { if (s == null) throw new ArgumentNullException(); // обратите внимание, `Inner` без параметров IEnumerable Inner() { foreach (var v in s) if (v % 2 != 0) yield return v; } return Inner(); }
Исключение будет при этом брошено сразу, при вызове GetOdd, а не при перечислении. Очевидно, что внутренняя функция имеет смысл лишь для GetOdd, так что не стоит заводить для неё отдельную функцию на внешнем уровне, пусть даже и приватную.

Комментариев нет:

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