Полезные Бесполезные Возможности C#

В 53 выпуске подкаста DotNet & More ( сайт , YouTube ) мы обсудили возможности C# 10 и рассмотрели их применимость в будущем.

И только тогда возник вопрос: все ли конструкции языка из C# 1, 2, 3, 4 и т.д. мы применим? И даже если они ужасно устарели, есть ли ситуации, в которых их можно использовать?




Говоря о C#, я бы разделил его жизненный путь на 2 вехи: до C#6 и начиная с C#6. Именно с выпуском шестой версии Microsoft изменила свой подход к проектированию языка и стала активно прислушиваться к мнению сообщества.

Именно поэтому я хотел затронуть не столько «современный» C#, сколько C# 2012 года, который многие программисты «старой школы» считают True C# (а поскольку этим ребятам уже 30+ лет, они занимают позиции командного лиды и продакт-менеджеры соответственно определяют технологический стек проекта).

Если посмотреть на C# 5 с точки зрения ненужных возможностей, то их будет не так много.

На мой взгляд, я бы отметил следующее:

И тем интереснее попытаться найти применимость этих языковых конструкций в 2021 году.



Оператор делегата

То, что этот оператор беззастенчиво устарел, подтверждают сами Microsoft в своей справке:

Полезные бесполезные возможности C#

Но внутренний манчкин не терпит такой траты ключевых слов.

Действительно ли лямбда-выражения во всех отношениях лучше такого «лампового» делегата? Нет, есть одна особенность: в операторе делегата мы можем опускать параметры метода, если они нам не нужны.

В лямбда-выражениях можно использовать подчеркивание (_), но если у функции много параметров, то код, который вы получите, будет неприятен с эстетической точки зрения.

Давайте сравним:

  
  
  
  
  
  
  
  
  
  
   

Func<int, string, double, User, bool> allowAll = (_, _, _, _) => true;

против

Func<int, string, double, User, bool> allowAll = delegate { return true; };

Этот пример, честно говоря, очень вдохновляет. Ведь мы нашли применение такому «динозавру»! Конечно, я бы не рискнул использовать такой подход в реальных проектах: эстетика есть эстетика, но разочарование других разработчиков такими синтаксическими конструкциями может стоить не один человеко-час.

И мне бы не хотелось тратить время команды.



Динамический тип

Ни для кого не секрет, что динамический тип был добавлен в C# для облегчения работы с COM (см.

https://stackoverflow.com/questions/14098958/c-sharp-dynamic-com-objects ).

Конечно, кто-то скажет, что разработчики решили порадовать программистов и превратить C# в такой красивый и удивительный язык, как JavaScript, но давайте будем честны сами с собой, в 2010 году Microsoft пошла по пути Балмера с построением собственной закрытой экосистемы, уделяя минимальное внимание мир.

И поэтому не стоит сбрасывать со счетов динамику, COM жив, и будет жить, пока жив MS Office. Иногда вам также приходится использовать динамический режим при работе с библиотеками, использующими эту возможность, например, ASP Net MVC. Однако я бы не сказал, что такая практика вообще широко распространена.

Но если вы не разрабатываете надстройки для Excel, есть ли смысл использовать динамические типы? На самом деле да: динамический тип чрезвычайно удобен для прототипирования.

Когда мысль летит вперед, переход к DTO-файлу с полями просто прервет ее.



dynamic entity = new ExpandoObject(); // the genius code with 'entity' variable

Такой подход чрезвычайно удобен при обсуждении реализации и «выкидывании» вариантов решения задачи: вместо рисования на доске можно сразу написать примерный вариант решения в коде и, более того, запустить это решение.

В противном случае придется признать, что динамика — это огромный кусок ненужной функциональности.

И это очень печально, ведь сочетание статической и динамической типизации может стать невероятно мощным инструментом.

На мой взгляд, этого не произошло потому, что была потеряна одна из важнейших особенностей языков программирования, таких как JavaScript и Python: интерпретируемость.

Хотя среда выполнения динамического языка имеет интерпретацию, у нас нет возможности избежать перекомпиляции сборки, а это означает, что мы не можем вносить изменения во время работы приложения.

Например, разработчики 1С очень часто пишут код инкрементно — код вводится в обработчик нажатия кнопок по мере прохождения интерпретатора, поэтому для какого-либо мелкого исправления нет необходимости перезапускать приложение.

В C# вам придется компенсировать это подходом Test First.

Выражение запроса

Этот синтаксический сахар вызвал много споров 10 лет назад, и это хорошо видно в коде, написанном в те времена, но сегодня всем совершенно очевидно, что код похож на

var teenagers = from u in users where u.Age is > 10 and < 18 select u.Name;

гораздо сложнее с точки зрения расширяемости и поддержки, чем «точечная запись».



var teenagers = users .

Where(u => u.Age is > 10 and < 18) .

Select(u => u.Name);

Однако есть по крайней мере один случай, когда выражение запроса наносит ответный удар: работа с несколькими SelectManys. Давайте представим, что мы хотим найти друзей нашего пользователя.

Запишем в разных обозначениях:

private IEnumerable<User> GetFriends(string myName) => _users .

Where(u => u.Name == myName) .

SelectMany(u => u.Friends);



private IEnumerable<User> GetFriends(string myName) => from u in _users where u.Name == myName from f in u.Friends select f;

В этом случае Dot Notation определенно лучше, по крайней мере, по количеству строк.

Что делать, если нам нужно запросить друзей друзей?

private IEnumerable<User> GetFriendsOfFriends(string myName) => _users .

Where(us => us.Name == myName) .

SelectMany(u => u.Friends.SelectMany(f => f.Friends) );



private IEnumerable<User> GetFriendsOfFriends(string myName) => from u in _users where u.Name == myName from f in u.Friends from fof in f.Friends select fof;

Здесь ситуация гораздо интереснее, поскольку в первом случае избежать вложенности не удается.

И это может стать довольно серьезной проблемой в случае действительно больших запросов, например, если вам нужно найти не просто друзей друзей, а друзей друзей друзей друзей:

private IEnumerable<User> GetFriendsOfFriendsOfFriendsOfFriendsOfFriends(string myName) => _users .

Where(us => us.Name == myName) .

SelectMany(u => u.Friends.SelectMany(f => f.Friends.SelectMany(fof => fof.Friends.SelectMany(fofof => fofof.Friends.SelectMany(fofofof => fofofof.Friends) ) ) ) );



private IEnumerable<User> GetFriendsOfFriendsOfFriendsOfFriendsOfFriends(string myName) => from u in _users where u.Name == myName from f in u.Friends from fof in f.Friends from fofof in fof.Friends from fofofof in fofof.Friends from fofofofof in fofofof.Friends select fof;

Конечно, этот пример немного надуман, но в кровавом предприятии так не бывает. Главное, что в C# есть инструмент «выпрямления» вложенности LINQ и в ряде случаев он просто незаменим.

P.S.: Также можно отметить, что использовать Join гораздо удобнее в виде Query Expression, чем в Dot Notation, но на мой взгляд, все зависит от привычки.



Заключение

Будем честны, приведенные примеры — это, по сути, стрельба по воробьям из пушки.

Нет смысла тратить время на изучение этих устаревших синтаксических конструкций просто потому, что в некоторых случаях они будут немного полезны.

И даже если вы мастер Query Expression, эксперт DLR и не представляете своей жизни без делегата, не стоит усложнять жизнь коллегам.

Но все же не с прагматической, а с романтической точки зрения приятно осознавать, какую мощь содержит в себе C#, какие богатые возможности, позволяющие выражать свои мысли более кратко и ясно.

Теги: #C++ #.

NET #LINQ #делегат #dlr

Вместе с данным постом часто просматривают: