Страницы

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

вторник, 2 октября 2018 г.

Для чего действительно нужны локальные функции?

Вот в этом вопросе поднимается вопрос о планируемых нововведения в c# версии 7.
В частности меня заинтересовали так называемые локальные функции
В ответе @VladD есть пример как может быть использована локальная функция
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(); }
но мне кажется данный пример слегка натянутым, в данном случае можно было бы наверно и обойтись без функции Inner()
Так же мне непонятно следующее высказывание:
По поводу локальных функций, мне кажется, часто, наоборот, приватные функции классов используются как костыль на отсутствие локальных функций. Часто в приватную функцию выносится хелпер из одной функции, не имеющий значения внутри класса. Локальная функция — более правильный путь для таких функций.
У себя в коде я оформляю отдельными методами код на основании следующих правил:
Функционал который может быть повторно использован; Вынесения функционала в отдельный метод что бы поддерживать стройность функции; Прочее

Например:
public IEnumerable GetPhonesForNotice(requestId) { var requestState = GetStateOfRequest(int requestId); switch(requestState) { case "Открыта": { return getRecipientsForNewRequest(int requestId); break; } case "Закрыта": { return getRecipientsForClosedRequest(int requestId); break; } } }
private IEnumerable getRecipientsForNewRequest(int requestId) { } private IEnumerable getRecipientsForClosedRequest(int requestId) { }
Если я правильно понял то тогда в новой версии языка функции getRecipientsForNewRequest(int requestId), getRecipientsForClosedRequest(int requestId) можно будет реализовать внутри главной функции GetPhonesForNotice(int requestId) но я не понимаю чем это правильней/лучше текущего варианта?


Ответ

Мне кажется вот как.
Вопрос не в технической стороне дела. (Хотя технические отличия есть, они приведены в конце ответа.) С технической стороны, можно организовать такую же приватную функцию, в которую передавать все параметры явно. А если внутренняя функция должна иметь меньше параметров (например, из-за того, что требуется специфическая сигнатура), можно построить closure. Все технические вопросы решаемы и в рамках старых возможностей. (Точно так же как можно было бы обойтись без свойств и использовать пару функций, без классов, и передавать this в методы явно и т. д.)
Дело в том, что мы хотим не загрязнять хелпер-функциями класс, а положить их внутри того, к чему они относятся. Это полная аналогия идеи о том, что класс должен содержать всё то, что необходимо ему для работы, и не требовать «внешнего» управления. Точно так же и тут, функция содержит внутри себя то, что нужно для работы, а приватные вспомогательные методы получаются необходимы лишь там, где они не ограничены смыслом одной функции — то есть, там, где они осмысленны в рамках всего класса
С таким подходом, да, локальные переменные некоторым образом заменяют поля класса, так что в ситуациях, когда раньше вам нужно было создавать внутренний класс и вызывать в нём методы, теперь можно сделать то же самое удобнее с локальными функциями.
Кроме того, уменьшение обвязочного кода наподобие неявной передачи переменных всегда важно, так обвязочный код — возможность ошибиться.

В качестве ещё одного мотивирующего примера:
void SortBy(List list, Func expr, bool ascending) { int comparerAscending(T t1, T t2) { return expr(t1).Compare(expr(t2)); }
int comparerDescending(T t1, T t2) { return expr(t2).Compare(expr(t1)); }
list.Sort(ascending ? comparerAscending : comparerDescending); }
Без локальных функций вам пришлось бы создавать внутренние делегаты.

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

Дополнение: Давайте рассмотрим, кроме логических, отличия между локальными функциями и уже существующими средствами языка.
Отличие от приватной нелокальной функции состоит, кроме «скрытого» имени, в том, что локальная функция «видит» переменные, объявленные в охватывающей функции до неё. Таким образом, отпадает необходимость передавать параметры в локальную функцию явно, и значит, мы можем более свободно управлять её сигнатурой. (Это может быть важно, см. пример с SortBy.)
Отличие локальной функции от аналогичного локально объявленного делегата с лямбда-функцией состоит в том, что
делегатная переменная может быть переопределена, имя локальной функции не может быть перепривязано лямбда-функции требуют хитрого синтаксиса для реализации рекурсивного вызова, который таки ломается при последующем изменении делегатной переменной (Y-комбинатор не предлагать!), у локальных функций проблем не возникает лямбда-функция не может быть генератором (yield return) вы не можете объявить обобщённую лямбда функцию (Func f = t => 1; не скомпилируется для неизвестного типа T), с локальными функциями проблем не возникает производительность: делегат означает лишнюю аллокацию экземпляра делегатного типа; локальная функция этого недостатка лишена, и дополнительная аллокация нужна лишь для случая нетривиального замыкания.
Литература: C# Design Notes for May 20, 2015

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

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