Страницы

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

понедельник, 25 ноября 2019 г.

Объясните смысл новшеств 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)
    {

    }
}

    


Ответы

Ответ 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, то в первом сниппете мы вернем не элемент списка, а его свежесозданну копию. Т.е. если мы ее (копию) модифицируем, то в оригинальном списке изменение не отразится. Это не совсем то поведение, которое ожидается от ф-ции.

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

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