В новой версии превью 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)
{
}
}
Ответы
Ответ 1
По первой части, это интересно для типов-значений.
С ссылочными типами нет особой разницы, работаете вы с объектом по оригиналу ссылк
или по её копии. Но с типами-значениями, такими как 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, так что не стоит заводить для неё отдельную функцию на внешнем уровне, пусть даже и приватную.
Ответ 2
Я не претендую на полную правдивость, но с логической точки зрения все так:
Если мы уберем ref, то в первом сниппете мы вернем не элемент списка, а его свежесозданну
копию. Т.е. если мы ее (копию) модифицируем, то в оригинальном списке изменение не отразится. Это не совсем то поведение, которое ожидается от ф-ции.
Комментариев нет:
Отправить комментарий