Комментарии 19
Немножко дико выглядит код после return. Вы по какой-то причине так писали?
Мне кажется, для читабельности лучше было бы использовать локальную функцию после её объявления.
Это похоже на распространенную в последнее время практику, когда приватные методы располагаются в классе после публичных.
На мой взгляд, неудобно, т.к. когда нужно узнать, что делает тот или иной вызываемый метод, приходится "прыгать" вперед (вниз), а не назад (вверх).
Т.е., идет как бы забегание вперед.
Но если такую практику принять, то и локальные функции должны располагаться после основного блока кода.
Разумеется это не всегда так, иногда разработчик уже знает что делает класс и хочет разобраться в конкретном методе, но тогда, скорее всего, он уже знает как и что ему конкретно искать.
Это похоже на распространенную в последнее время практику, когда приватные методы располагаются в классе после публичных.
Некоторые вообще следуют принципам SOLID и выделяют публичную часть класса в отдельный интерфейс.
На мой взгляд, неудобно, т.к. когда нужно узнать, что делает тот или иной вызываемый метод, приходится "прыгать" вперед (вниз), а не назад (вверх).
При использовании IDE этой проблемы нет. В VS вообще можно прыгать через Alt+F12 с сохранением истории переходов.
Некоторые вообще следуют принципам SOLID и выделяют публичную часть класса в отдельный интерфейс.
Это само собой. Поэтому постановка задачи в формате "вначале публичные методы, чтобы видеть, что делает класс" не кажется убедительной.
В случае наличия интерфейса, при рассмотрении реализации, скорее первыми хочется увидеть детали реализации, чтобы при просмотре публичных методов уже понимать, как что работает.
При использовании IDE этой проблемы нет. В VS вообще можно прыгать через Alt+F12 с сохранением истории переходов.
Тем не менее, для аккуратности и читаемости кода лучше методы группировать.
Сейчас в тренде группировать по области видимости в порядке, начиная с публичных, хотя раньше было не так.
Это прослеживается и в новых MS исходниках, например, шаблоне Web API Core приложения.
В целом, это дело моды и вкуса.
В итоге для таких команд сложность языка растет, а половина плюсов локальных функций исчезает.
В сильно кровавом энтерпрайзе, в мобилках, в играх, где нужно думать о скорости выполнения, локальные функции могут быть полезны.
Небольшой нетехнический момент: в раздел 101 можно добавить пункт о форматировании кода. Например, в коде есть публичный метод на 30 строк, разбитый на
public void DoSomething()
{
DoFirstPart();
DoSecondPart();
DoThirdPart();
}
..
// Private methods Do*Part here
В итоге в классе лежат 4 метода. Если вынести Do*Part методы в локальные функции — остается лишь один метод, сор из DoSomething не выносится.
часто без активного использования async и Enumerable (везде используется List).
То есть, Linq вообще не используют?
В итоге в классе лежат 4 метода. Если вынести Do*Part методы в локальные функции — остается лишь один метод, сор из DoSomething не выносится.
А вот это, ИМХО, перебор, 4 локальных функции…
То есть, Linq вообще не используют?
Наверное имелось в виду, что yield return не используют.
Как выше заметил Naglec — не используют yield return.
По поводу функций вопрос открыт. В любом случае у нас есть три выделенных куска кода. Какая разница где их хранить, в теле класса как приватные методы, или в теле метода как локальные функции? В первом случае мы «засоряем» интерфейс класса, пускай и в приватной части, во втором случае мы «засоряем» лишь метод. В случае ЛФ сразу видно где код используется, в случае приватных методов проще переиспользовать код.
Конечно, это выбор отдельно взятого человека/команды. Но на мой вкус — это перебор.
В первом случае мы «засоряем» интерфейс класса, пускай и в приватной части, во втором случае мы «засоряем» лишь метод. В случае ЛФ сразу видно где код используется, в случае приватных методов проще переиспользовать код.
Но в случае ЛФ сразу возрастает уровень вложенности, что тоже читабельности не добавляет.
Лично я пока вижу адекватный сценарий использования ЛФ только в виде описания небольшой логики (в пределах 5-10 строк), которая нигде за пределами метода использоваться не будет. И, наверное, не более одной ЛФ на метод.
Спасибо за подробный обзор локальных функций!
Локальные функции и анонимные блоки кода имеют разную семантику.
Анонимные блоки на то и анонимные, чтобы использовать их как лямбда-функции (передавать как стратегии в метод или класс).
А локальные функции это локальные функции.
И в силу отличий в семантике и технических причин, анонимные блоки не могут заменить собой локальные функции во всех случаях.
На случай с итератором я сам наталкивался.
Оказывается, этих случаев больше, и не все они столь очевидны, как случай с итератором.
И не все столь явно проявляются: одно дело, как тот же итератор, не компилируется, другое — когда компилируется, но отличаются производительность и поведение.
И еще, на мой взгляд, важность локальных функций незаслуженно недооценена, и здорово, что они наконец-то появились.
Но есть сомнения, что они приживутся — появились слишком поздно, коммьюнити C# зрелое, в основной своей части консервативное, общепринятые подходы и практики наработаны.
И вряд ли в командах локальные функции будут без вопросов проходить через ревью, хотя область их применения вполне просматривается:
- В основном методе есть повторяющиеся участки, которые нецелесообразно выносить во внешний метод, т.к. новый (локальный) метод имеет смысл только в контексте основного.
Вынесение такого участка по внешний метод привносит "замусоривание" кода класса и риск того, что в дальнейшем кто-то может посчитать такой метод самодостаточным, дорабатывать и вызывать его в других методах класса. - Бывает, что код метода нельзя сделать короче одного-двух экранов, но в нем просматриваются отдельные блоки, которые для читаемости основной логики есть смысл вынести в отдельные методы.
Тогда можно провести декомпозицию основного метода, вынеся блоки в локальные методы (локальные — по тем же причинам).
А ещё дело в том, что локальные методы имеют ненулевой оверхед (описано в публикации) при обращении к сущностям вне локального метода. И учитывая, что, в отличие от лямбд в С++, захват осуществляется неявно, его довольно трудно проследить в сложном коде.
Откуда вообще возникают вопросы по группировке методов?
В т.ч. тогда, когда методов уже многовато, и они начинают образовывать группы, выполняющие уже разные обязанности, и класс нуждается в декомпозиции.
Соответственно, и для случая, когда декомпозируется метод на методы, имеющие смысл только в его контексте, можно реализовать декомпозицию двумя способами (при условии, что не выносим на верхний уровень "подметоды"):
- Локальные методы.
- Отдельная сущность, для решения задачи, которую до этого решал большой метод. И поскольку сущность эта будет небольшая (разделили метод на 2-3-4 метода и вынесли их в новую сущность), то и вопрос группировки становится неактуальным.
Какой метод выбрать — видимо, решать на месте, в зависимости от количества и объема "подметодов".
Анализируем локальные функции в C# 7