В новой версии превью 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, так что не стоит заводить для неё отдельную функцию на внешнем уровне, пусть даже и приватную.