.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День триста двадцать седьмой. #Оффтоп
Все выходные зависал на Codewars. Не то, чтобы я не знал раньше об этом сайте, просто как-то не доходили руки. А тут, как обычно, наткнулся в ютубе на видео парня, который работает в Гугле. Так вот, он рассказывал про то, как попал в Гугл, начав кодить за полгода до этого. Да, я тоже сначала не поверил, пока он не показал свой рейтинг на Codewars в почти 4500. Впрочем, про этого парня чуть позже напишу. А пока я решил проверить, насколько тяжело столько набрать.
Я уже писал про подобный сайт. Проблема в том, что там тебе сначала даются элементарные задачи, которые «щёлкаешь» и вроде даже входишь в раж, но в итоге их столько, что это быстро надоедает. Понятно, что начинающим это помогает «набить руку», но опытным после дцатой задачи на поиск элемента в массиве становится скучно. А поскольку прогресс построен на «пути от простого к сложному», то сложные задачи закрыты, пока не решены простые. Поэтому я заниматься там как-то быстро забросил. Да и Codewars тоже не спешил пробовать по этой же причине: пока доберёшься до интересных заданий, потратишь кучу времени.
Однако, всё оказалось совсем не так. При регистрации можно указать свой уровень: начинающий/джун/мидл/сениор, - и задачи выдаются соответственно уровню. То есть рейтинг то твой, конечно, сначала нулевой, и самый низкий 8kuy (ку? кю? куй?) уровень, короче, но задачи система даёт уже не элементарные, а чуть более сложные, и можно быстро добраться до более высокого уровня и более интересных задач. Кроме того, можно в принципе взять любую задачу любого уровня (хоть самого высокого 1го) и решать её.
Задачи построены по методу разработки через тестирование. Вам даётся задача и открытый список из 2-3 тестов, которые программа должна пройти, и собственно поле для написания кода. Хотя, без IntelliCode и автоформатирования писать там довольно грустно, поэтому писал в VS, а потом туда копировал. Да, испорчен, признаю. Что интересно, после этого можно попытаться отправить своё решение, и тогда код будет протестирован расширенным списком тестов, которые от вас скрыты. То есть вы не видите, что не так, просто факт, что какой-то тест выдал false вместо true. Это позволяет не подгонять решение под ответ, а действительно решать задачу.
За выходные набрал 185 баллов рейтинга и дошёл до 6 уровня. На задачи, как мне кажется, я тратил уйму времени. Дело в том, что в веб-разработке, которой я занимаюсь, довольно редко приходится развивать «мелкую моторику» (парсить строки, работать с байтами, писать низкоуровневые алгоритмы и т.п.). Зачастую это всё уже давно решено, написано в библиотеках, и ты просто вызываешь метод одной библиотеки, второй библиотеки, коллекции обрабатываешь LINQом и складываешь приложение, как паззл. А тут приходится попотеть: например, написать упрощённый интерпретатор для языка BrainFuck или разбить квадрат числа на сумму квадратов. И оказывается, что то, чем редко пользуешься, забывается очень быстро. Так что поставил себе цель периодически практиковаться там, а то даже стыдно перед собой.

Кстати, если вы ищете, какую бы программу написать себе для портфолио или просто для практики, Codewars, по-моему, идеальный вариант. Берёте задания 1-2го уровня – а это зачастую уже полноценные приложения - и вперёд. Языков программирования там, кстати, целая куча, поэтому подкачать скиллы можно почти в любом. А вот сам сайт, конечно, только на английском.
День триста двадцать восьмой. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
4. Что делает ConfigureAwait(false)?
Метод ConfigureAwait, принимающий параметр continueOnCapturedContextпродолжитьВЗахваченномКонтексте»), возвращает структуру ConfiguredTaskAwaitable, которая содержит специальным образом настроенный «ожидатель» (awaiter). Поскольку await может использоваться с любым типом, соответствующим паттерну awaitable (не только с Task напрямую), возврат другого типа вместо задачи позволяет изменить логику захвата контекста/планировщика (см. предыдущий пост) на что-то вроде этого:
object scheduler = null;
if (continueOnCapturedContext)
{
// захват контекста/планировщика
}
Другими словами, получая значение false, объект не захватывает текущий контекст/планировщик, даже если они существуют.

Зачем использовать ConfigureAwait(false)?
Этот метод используется, чтобы избежать принудительного вызова метода обратного вызова в исходном контексте или планировщике. Это даёт несколько преимуществ:
1. Улучшает производительность. Требуется дополнительная работа, чтобы поставить в очередь метод обратного вызова вместо того, чтобы просто вызывать его. Кроме того, некоторые оптимизации времени выполнения, не могут быть использованы. Иногда даже проверка текущих контекста и планировщика (что включает в себя доступ к статическим потокам) могут добавить измеримые накладные расходы. Если код после await фактически не требует запуска в исходном контексте, использование ConfigureAwait(false) поможет избежать всех этих затрат.
2. Позволяет избежать взаимных блокировок. Взаимная блокировка чаще всего возникает при использовании кода, блокирующего текущий поток (при вызове .Wait(), .Result или .GetAwaiter().GetResult() на задаче, внутри которой используется await). Если в текущем контексте явно или неявно ограничено число параллельных операций, либо если код блокирует UI-поток, получается ситуация, когда контекст/поток заблокирован в ожидании завершения асинхронной задачи, а метод обратного вызова этой задачи ставится в очередь для вызова на заблокированном контексте/потоке и поэтому не может быть выполнен (взаимная блокировка). Такая ситуация может возникать, когда ресурсы ограничены любым способом и так или иначе оказываются заняты. Если задача использует ConfigureAwait(false), метод обратного вызова не ставится в очередь в исходный контекст, что позволяет избежать сценариев взаимоблокировки.

Зачем использовать ConfigureAwait(true)?
Поскольку захват контекста происходит по умолчанию, то ConfigureAwait(true) практически никогда не используется, за исключением случаев, когда вы хотите показать, что вы намеренно не используете ConfigureAwait(false) (например, чтобы отключить предупреждения статического анализа кода). Метод ConfigureAwait принимает логическое значение, поскольку существуют некоторые нишевые ситуации, в которых вы хотите передать переменную для управления конфигурацией. Но в 99% случаев используется жестко закодированное значение false. Поэтому вызовы ConfigureAwait(true) можно безболезненно удалять.

Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста двадцать девятый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
5. Когда использовать ConfigureAwait(false)?
Это зависит от того, реализуете ли вы код уровня приложения или код библиотеки общего назначения. При написании приложений обычно предпочтительно поведение по умолчанию. Если модель приложения или среда (например, Windows Forms, WPF, ASP.NET Core и т. д.) публикует собственный SynchronizationContext, почти наверняка для этого есть веская причина: контекст позволяет коду правильно взаимодействовать с моделью приложения/средой. Поэтому, если вы пишете обработчик событий в приложении Windows Forms, модульный тест в xunit, код в контроллере ASP.NET MVC, то независимо от того, опубликовала ли модель приложения SynchronizationContext, вы предпочли бы использовать его, если он существует. Поэтому по умолчанию используется ConfigureAwait(true). Вы просто используете await, и обратные вызовы/продолжения отправляются обратно в исходный контекст, как и должны. Общее правило: если вы пишете код уровня приложения, не используйте ConfigureAwait(false). Если вернуться к обработчику события Click в примере с кнопкой, то код
downloadBtn.Text = text;
должен быть выполнен в исходном контексте. В этом примере вызов асинхронного кода с
ConfigureAwait(false)
приводил бы к ошибке. То же самое относится и к коду в классическом приложении ASP.NET, зависящем от HttpContext.Current. Использование ConfigureAwait(false), а затем попытка использования HttpContext.Current скорее всего приведёт к проблемам.
И наоборот, библиотеки общего назначения являются «универсальными» отчасти потому, что их не волнует среда, в которой они используются. Вы можете использовать их в веб-приложении, в клиентском приложении или в тесте, это не имеет значения, поскольку код библиотеки не зависит от модели приложения, в которой он может быть использован. Эта независимость также означает, что код не собирается делать что-то, что должно взаимодействовать с моделью приложения строго определённым образом. Например, он не будет получать доступ к элементам управления UI, потому что библиотека общего назначения ничего не знает о них. Поскольку отпадает необходимость запускать код в какой-либо конкретной среде, мы можем избежать принудительного возврата продолжений/обратных вызовов в исходный контекст через ConfigureAwait(false), получая преимущества как в производительности, так и в надёжности. Здесь общее правило такое: если вы пишете код библиотеки общего назначения, используйте ConfigureAwait(false). Вот почему, например, практически любое (за некоторыми исключениями) ожидание в библиотеках среды выполнения .NET Core использует ConfigureAwait(false).
В любых правилах, конечно, могут быть исключения. Например, стоит обдумать решение для библиотек общего назначения с API, принимающим делегаты. В таких случаях пользователь библиотеки передаёт для вызова в библиотеке код приложения. Рассмотрим, например, асинхронную версию LINQ метода Where:
public static async IAsyncEnumerable<T> WhereAsync(this IAsyncEnumerable<T> source, Func<T, bool> predicate);
Нужно ли здесь вызывать predicate в исходном SynchronizationContext вызывающей стороны? Это зависит от реализации WhereAsync. По этой причине можно отказаться от использования ConfigureAwait(false).

Гарантирует ли ConfigureAwait(false), что обратный вызов не будет выполняться в исходном контексте?
Нет. Он гарантирует, что он не будет поставлен в очередь в исходный контекст… но это не означает, что код после
await task.ConfigureAwait(false) 
не будет исполнен в исходном контексте. Это связано с тем, что код, расположенный после уже завершённых задач, просто продолжает исполняться синхронно, а не ставится в продолжение/обратный вызов. Таким образом, если вы ожидаете задачу, которая уже выполнена к тому времени, когда вызывается await, независимо от того, использовали ли вы ConfigureAwait(false), последующий код сразу продолжит выполняться в текущем потоке, в текущем контексте.

Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста тридцатый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
6. Можно ли использовать ConfigureAwait(false) только в первом ожидании методе, но не в остальных?
В общем случае нет. Смотрите предыдущий пост. Если ожидание
await task.ConfigureAwait(false) 
включает в себя задачу, которая уже завершена (что на самом деле случается очень часто), то ConfigureAwait(false) будет бессмысленным, и поток продолжит выполнять код метода дальше в том же контексте.
Исключением является ситуация, когда вы точно знаете, что первое ожидание всегда завершается асинхронно, а обратному вызову задачи не нужен текущий контекст и планировщик задач. Например, CryptoStream в библиотеках .NET Runtime хочет, чтобы его потенциально требовательный к вычислительным ресурсам код не выполнялся как часть синхронного вызова вызывающей стороны, поэтому он использует настраиваемый ожидатель, чтобы весь код после первого ожидания выполнялся в потоке из пула. Однако даже в этом случае можно заметить, что в следующем ожидании всё равно используется ConfigureAwait(false). Технически это не является необходимым, но это делает обзор кода намного проще, так как при чтении этого кода не нужно ломать голову, почему в остальных случаях нет вызова ConfigureAwait(false).

Можно ли использовать Task.Run, чтобы избежать использования ConfigureAwait(false)?
Да. В следующем коде
Task.Run(async delegate {
await SomethingAsync();
});
использование ConfigureAwait(false) для SomethingAsync не имеет смысла, т.к. делегат, переданный в Task.Run, будет выполняться в потоке из пула без учёта контекста. Кроме того, Task.Run неявно использует TaskScheduler.Default, что означает, что запрос TaskScheduler.Current внутри делегата также возвратит Default. Это означает, что ожидание будет демонстрировать одинаковое поведение независимо от того, использовался ли ConfigureAwait(false). Кроме того, нет никаких гарантий относительно того, что может делать код внутри этого делегата. Если у вас есть код:
Task.Run(async delegate
{
SynchronizationContext.SetSynchronizationContext(new SomeCoolSyncCtx());
await SomethingAsync();
});
тогда код внутри SomethingAsync на самом деле увидит в SynchronizationContext.Current экземпляр SomeCoolSyncCtx, и как это ожидание, так и любые ненастроенные ожидания внутри SomethingAsync будут отправлять обратные вызовы в этот контекст. Поэтому, чтобы использовать этот подход, вам необходимо понимать, что может и что не может делать код, который вы ставите в очередь, и могут ли его действия что-то испортить.
Также заметим, что при этом подходе необходимо создавать/ставить в очередь дополнительный объект задачи. Это может иметь или не иметь значения для вашего приложения или библиотеки в зависимости от того, критична ли для вас производительность.
Кроме того, имейте в виду, что такие уловки могут вызвать больше проблем, чем принесут пользы, и иметь другие непредвиденные последствия. Например, инструменты статического анализа (например, анализаторы Roslyn) были написаны для отметки ожиданий, которые не используют ConfigureAwait(false), предупреждением CA2007. Если вы включили такой анализатор, но затем применили хитрость, подобную этой, просто чтобы избежать использования ConfigureAwait, есть большая вероятность, что анализатор всё равно пометит его предупреждением.

Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста тридцать первый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
7. Нужно ли использовать ConfigureAwait(false) при вызове GetAwaiter().GetResult()?
Нет. ConfigureAwait влияет только на обратные вызовы. В частности, паттерн awaiter требует, чтобы ожидатели предоставили свойство IsCompleted, метод GetResult и метод OnCompleted. ConfigureAwait влияет только на поведение OnCompleted, поэтому, если вы просто напрямую вызываете метод GetResult() разницы в поведении не будет. Поэтому можно заменить
task.ConfigureAwait(false).GetAwaiter().GetResult();
на
task.GetAwaiter().GetResult();
если, конечно, вы действительно хотите блокировать текущий поток.

Говорят, что ConfigureAwait(false) больше не требуется в .NET Core. Это правда?
Нет. Он необходим при работе в .NET Core по тем же причинам, что и при работе в .NET Framework. В этом отношении ничего не изменилось.
Однако изменилось поведение некоторых сред в части публикации собственного контекста. Например, если ASP.NET в .NET Framework имеет свой собственный SynchronizationContext, то в ASP.NET Core нет. Это означает, что код, работающий в приложении ASP.NET Core по умолчанию, не увидит пользовательский SynchronizationContext, что исключает необходимость вызова ConfigureAwait(false) в такой среде.
Но это не означает, что пользовательский SynchronizationContext или TaskScheduler никогда не будут предоставлены. Если какой-либо пользовательский код (или код библиотеки), используемый вашим приложением, задаёт пользовательский контекст, то даже в ASP.NET Core ваши вызовы await могут захватывать контекст или планировщик, что может повлечь необходимость использования ConfigureAwait(false).

Можно ли использовать ConfigureAwait в await foreach для IAsyncEnumerable?
Да. await foreach работает с шаблоном, поэтому он может использоваться для перечисления не только IAsyncEnumerable<T>, но и всего, что соответствует шаблону. Библиотеки .NET Runtime включают метод расширения ConfigureAwait для IAsyncEnumerable<T>, который возвращает сконфигурированную структуру, соответствующую шаблону. Когда компилятор генерирует вызовы методов MoveNextAsync и DisposeAsync перечислителя, структура выполняет ожидание желаемым способом.

Можно ли использовать ConfigureAwait в await using для IAsyncDisposable?
Да, хотя есть небольшой нюанс. Как и в случае IAsyncEnumerable<T>, библиотеки .NET Runtime предоставляют метод расширения ConfigureAwait для IAsyncDisposable. И await using будет успешно работать с ним, поскольку он реализует соответствующий шаблон (предоставляет метод DisposeAsync):
await using (var c = new MyAsyncDisposableClass().ConfigureAwait(false)) {…}
Проблема здесь в том, что тип c теперь не MyAsyncDisposableClass, а System.Runtime.CompilerServices.ConfiguredAsyncDisposable, возвращаемый из метода расширения ConfigureAwait. Чтобы обойти это, вам нужно написать одну дополнительную строку:
var c = new MyAsyncDisposableClass();
await using (c.ConfigureAwait(false)){…}
Это расширяет область действия c, и, если это критично, можно обернуть всё это в фигурные скобки.

Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста тридцать второй. #ЗадачиНаСобеседовании
Встречаю много вопросов на тему, чего ждать на собеседовании, к чему готовиться, что нужно знать и т.п. Поскольку лично я работаю в одной компании уже 12 лет, то считать себя экспертом по собесам не могу. Поэтому лучше сошлюсь на хорошее видео на эту тему от АйТиБороды.
А в этом посте хотел бы привести одну из практических задач. Не знаю, как у нас, а на собеседованиях в заграничных «ИТ-гигантах» умение решать задачи ценится больше, чем просто знание технологий. Возможно, это и правильно. Как я упоминал ранее, я нашёл канал одного парня, его зовут Клемент Михалеску, который занимается интервью кандидатов в Гугле. Так вот, в одном видео (не спешите смотреть, чтобы не заспойлерить) мне понравилась задача, которую предлагают решить кандидату на собеседовании.
Дана коллекция точек на плоскости с координатами (x, y). См. рисунок ниже (он немного кривоват, но суть, думаю, понятна). Для простоты допустим, что все координаты целочисленные. Нужно написать функцию, которая будет вычислять количество прямоугольников, образованных этими точками. Очевидные правила:
- прямоугольник должен иметь 4 точки в углах,
- ненулевые длину и ширину,
- прямоугольники, образованные четырьмя одинаковыми точками A,B,E,D (см. рисунок) считаются как один (то есть ABED = BEDA = EDBA и т.п. – все считать за 1).
Итак, что мне понравилось в описании процесса интервью.
Во-первых, интервью длится около часа, и никто не ждёт от вас, что вы тут же напишете идеально правильный, компилирующийся и работающий код. Тем более, что на интервью даётся листок бумаги (документ Google Docs в видео), а не IDE. Интервьюеру интересен ход ваших мыслей. То есть вам даётся задание, вы начинаете рассуждать вслух, возможно записывать какой-то псевдокод. Хороший интервьюер (как в видео) будет рассуждать вместе с вами и задавать наводящие вопросы или корректировать, если вы полезете не в ту степь. Точно так же, как впоследствии вы будете работать и решать задачи в команде.
Во-вторых, сначала вам дают упрощённый вариант задачи. В данном случае допустим, что нужно посчитать прямоугольники, стороны которых параллельны осям координат. То есть пока мы не будем считать прямоугольник BDHF. Кстати, в видео «кандидат» блестяще с этим справляется, можете посмотреть решение до 20й минуты.
Кроме собственно решения интервьюер попросил пройти его по шагам, чтобы убедиться в правильности и понять ход мыслей самому, а также оценить «стоимость» решения как по времени, так и по занимаемой памяти. Вот это тоже нужно уметь делать.
Ну а дальше, если интервьюер действительно хочет оценить силу кандидата, то даст ему попробовать решить и усложнённый вариант. Собственно, Клемент поясняет далее во врезке, что от кандидата не ждут, что он сразу, прямо во время интервью, решит усложнённый вариант (чаще всего это не реально). Интервьюеру интереснее посмотреть на мыслительный процесс кандидата и то, как он справляется с действительно сложными задачами.
Интересно, что я практически уверен, что решение упрощённого варианта в видео правильное, а вот до решения усложнённого варианта, как мне показалось, они так и не дошли.
Итак, теперь нам нужно посчитать все возможные прямоугольники.

У меня есть теория решения, в которой я более-менее уверен, но пока не проверял. Приглашаю всех предлагать варианты решения в чате.
👍1
Интересны ли вам подобные задачи на канале?
Anonymous Poll
43%
Да
48%
Да, делать пост с объяснением решения позже
9%
Нет
День триста тридцать третий. #DesignPatterns
Паттерны проектирования
5. Паттерн «Наблюдатель» (Observer)
Существует два способа общения между двумя программными элементами. Компонент 1 может обратиться к Компоненту 2 для получения каких-то данных или выполнения некоторой операции, либо Компонент 2 может уведомить Компонент 1 о некотором событии. Первая модель взаимодействия называется pull-моделью, а вторая — push-моделью. В мире ООП push-модель реализуется с помощью паттерна «Наблюдатель».
Назначение: определяет зависимость типа «один ко многим» между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом и автоматически обновляются.
Причины использования:
При проектировании некоторого класса у разработчика всегда есть несколько вариантов реализации. Класс А может знать о существовании класса Б и уведомлять его о произошедшем событии. Или же класс А может быть наблюдаемым и уведомлять о некотором событии всех заинтересованных подписчиков. Использование наблюдателей уменьшает связанность между классами/модулями и упрощает повторное использование. Благодаря слабой связанности наблюдаемый объект и наблюдатели могут располагаться на разных уровнях абстракции или слоях приложения. Например, слой пользовательского интерфейса знает о модели, но модель не должна ничего знать о пользовательском интерфейсе.
Классическая диаграмма приведена на рисунке ниже:
- Observer — определяет интерфейс наблюдателя;
- Subject (наблюдаемый объект) — определяет методы подключения и отключения наблюдателей;
- ConcreteObserver — реализует интерфейс наблюдателя;
- ConcreteSubject — конкретный тип наблюдаемого объекта.

На платформе .NET практически невозможно встретить классическую реализацию паттерна «Наблюдатель». Существует несколько вариантов реализации:
1. С помощью делегатов (методов обратного вызова).
Самая простая форма наблюдателя. Для этого достаточно, чтобы класс потребовал делегат в аргументах конструктора и уведомлял вызывающий код с его помощью. Это позволяет гарантировать наличие наблюдателя, а также наблюдаемый объект может не только уведомлять об изменении своего состояния, но и требовать от делегата некоторого результата.
2. С помощью событий (events).
События представляют собой умную оболочку над делегатами, которая позволяет клиентам лишь подписываться на события или отказываться от подписки, а владельцу события — инициировать событие для уведомления всех подписчиков. Разница с первым вариантом в том, что интерфейс наблюдаемого объекта позволяет подписаться на событие любому числу подписчиков. При этом нет гарантии, что эти подписчики вообще будут. Подробнее о событиях
3. С помощью специализированных интерфейсов-наблюдателей.
В некоторых случаях, когда имеется несколько событий или делегатов, их удобно объединить в одном интерфейсе. Данный вариант очень похож на классическую версию паттерна «Наблюдатель», хотя обычно наблюдатель является единственным.
4. С помощью интерфейсов IObserver/IObservable.
Все перечисленные ранее варианты реализации паттерна «Наблюдатель» содержат одно ограничение: они плохо объединяются для получения более высокоуровневого поведения (not composable). Над событиями или делегатами невозможно выполнять операции, которые можно выполнять над последовательностями. С 4-й версии в .NET Framework появилась пара интерфейсов IObserver/IObservable с набором методов расширений, известных под названием реактивных расширений (Rx, Reactive Extensions). Интерфейс IObservable моделирует реактивные последовательности и позволяет работать с наблюдаемыми последовательностями через привычный LINQ-синтаксис. Он предполагает, что однородные события будут периодически повторяться. Наблюдаемый объект может уведомить о новом событии (OnNext), о том, что в процессе события произошла ошибка (OnError), или о том, что цепочка событий завершена (OnComplete). Пример: поток сетевых сообщений от клиента или сервера, координаты устройства и т.п.

Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 5.
День триста тридцать четвёртый. #ЗадачиНаСобеседовании
Ответ на задачу
Дана коллекция точек на плоскости с координатами (x, y). Для простоты допустим, что все координаты целочисленные. Нужно написать функцию, которая будет вычислять количество прямоугольников, которые образуют эти точки.

Как я уже говорил, у меня есть теория решения, в которой я более-менее уверен, но пока не проверял.
Итак, пусть у нас есть коллекция точек Point { int x; int y; }. Мы проходим по коллекции и для каждой точки добавляем в коллекцию вектор, начинающийся в этой точке и заканчивающийся в любой точке, которая выше этой точки (координата y больше координаты y текущей точки – y2 > y1), либо на том же уровне по высоте, но правее (x2 > x1, если y2 = y1). Таким образом для точки A (см. рисунок ниже) мы добавляем векторы AB, AC, AD, но не добавляем вектор AE (он будет учтён для точки E, как EA). Кроме того, для каждого вектора мы считаем его «направление» (x2 – x1, y2 – y1). Например, для точек A(0,0) и B(-1,4), направление вектора AB = (-1,4), а для AD = (11,7).
Таким образом получим коллекцию векторов
struct Vector
{
Point A { get; set; }
Point B { get; set; }
(int x, int y) Direction
{
get
{
return (B.X - A.X, B.Y - A.Y);
}
}
}
Теперь для каждого вектора из набора ищем вектор с равным ему направлением и начальной точкой, выбранной по тому же правилу (выше начальной точки текущего вектора или на том же уровне, но правее). Например, для вектора AB таким будет вектор CD (но для вектора CD мы не найдём вектора AB, т.к. A ниже C). Мы получили две параллельные и равные стороны (поскольку направления равны). Остался ещё один момент. У прямоугольников есть свойство, что их диагонали равны. Поэтому сравниваем длины диагоналей (не используем квадратный корень из формулы расстояния, чтобы не заморачиваться с округлением):
double AD = Math.Pow(vCD.A.X - vAB.B.X, 2) + Math.Pow(vCD.A.Y - vAB.B.Y, 2);
double BC = Math.Pow(vAB.B.X - vCD.A.X, 2) + Math.Pow(vAB.B.Y - vCD.A.Y, 2);
Если диагонали AD и BC равны, у нас есть прямоугольник.
Таким образом считаем количество для всех векторов и делим общее количество на 2, поскольку мы посчитаем прямоугольник ABCD дважды для векторов AB и AC).

По поводу стоимости: если у нас N точек, тогда в худшем случае у будет N(N-1)/2 векторов, то есть O(N^2) времени на создание векторов и O(N^4) на обход.

Как-то так. Если у вас есть вопросы или замечания, добро пожаловать в чат.
День триста тридцать пятый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
16. Замечание о комментариях.
На первом моём занятии по программированию в колледже преподаватель выдал по два бланка кода на Basic, написал на доске: «Напишите программу, принимающую результаты 10 игр в боулинг и рассчитывающую средний результат», - и вышел из класса. Насколько сложно это может быть? Я не помню свое окончательное решение, но я уверен, что в нем был цикл FOR/NEXT, и весь код был не длиннее 15 строк. Каждый бланк кода - да, дети, мы обычно писали код от руки перед тем, как вводить его в компьютер, - содержал около 70 строк. Мне было непонятно, почему преподаватель дал нам два бланка. Поскольку мой почерк всегда был корявым, я использовал второй бланк, чтобы аккуратно переписать свой код, надеясь получить пару дополнительных баллов.
К моему большому удивлению, когда я получил результат в начале следующего занятия, у меня была едва проходная оценка. (Это мучало меня потом всё время обучения в колледже.) Поперёк всего моего аккуратно переписанного кода было написано: «Без комментариев?»
Недостаточно того, чтобы мы с преподавателем знали, что должна делать программа. Частью задания было научить меня, что мой код должен быть понятен следующему программисту. Этот урок я не забыл.
Комментарии - не зло. Они так же необходимы в программировании, как основные конструкции кода. У большинства современных языков есть инструмент, анализирующий правильно отформатированные комментарии, чтобы автоматически генерировать документацию. Это очень хорошее начало, но этого недостаточно. Внутри вашего кода должны быть пояснения о том, что код должен делать. Кодирование по старому принципу: «Если было трудно писать, это должно быть трудно читать», - оказывает медвежью услугу вашему клиенту, вашему работодателю, вашим коллегам и в конечном итоге вам же в будущем.
С другой стороны, вы можете зайти слишком далеко в комментировании. Убедитесь, что ваши комментарии уточняют код, но не загромождают его. Добавляйте в код комментарии, объясняющие, что он должен выполнить. Заогловочные комментарии должны дать любому программисту достаточно информации, чтобы использовать ваш код без необходимости его чтения, в то время как встроенные комментарии должны помочь следующему разработчику исправить или улучшить его.
Однажды на работе я не согласился с проектом решения, принятым теми, кто был выше меня. Желая отомстить, как часто делают молодые программисты, я вставил текст письма, в котором указывалось, что нужно использовать их решение, в блок комментария в шапке файла. Оказалось, что менеджеры в этой компании перепроверяли код перед релизом. Так я познакомился со значением термина «действие, ограничивающее карьерный рост».

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Cal Evans
This media is not supported in your browser
VIEW IN TELEGRAM
День триста тридцать шестой.
С новым годом, дорогие читатели. Чистого вам кода без багов и ворнингов, зелёных тестов и шустрых IDE, адекватных заказчиков и блестящих идей для реализации их хотелок.
Много денег желать не буду, счастье не в них. Главное, чтобы их хватало на то, чтобы не волноваться об их недостатке, а получать удовольствие от работы. Ведь в этом настоящее счастье: заниматься любимым делом, приносить людям пользу. А если за это ещё и платят, так вообще жизнь удалась!
Счастья вам! С новым годом!
День триста тридцать седьмой. #Оффтоп
Пока отходите от празднования нового года, вот вам пара ненапряжных интересных видео.
Обычно объяснение того, как выполняется ваш высокоуровневый код в .Net на компьютере заканчивается на этапе JIT-компиляции. Но что же происходит потом?
В первом видео рассказывается о том, как программа вычисления чисел Фибоначчи на C (не пугайтесь, синтаксис очень похож на C#) компилируется в машинный код: https://www.youtube.com/watch?v=yOyaJXpAYZQ
А во втором видео уже скомпилированный код переводится в настоящие нули и единицы команд и аргументов и передаётся в самодельный компьютер, на котором и запускается: https://www.youtube.com/watch?v=a73ZXDJtU48

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

Источник: https://www.youtube.com/user/eaterbc
День триста тридцать восьмой. #DesignPatterns
Паттерны проектирования
6. Паттерн «Посетитель» (Visitor). Начало
Назначение:
описывает операцию, выполняемую с каждым объектом из некоторой иерархии классов. Паттерн «Посетитель» позволяет определить новую операцию, не изменяя классов этих объектов.
Причины использования:
Объектно-ориентированное программирование предполагает единство данных и операций. Обычно классы представляют некоторые операции, скрывая структуры данных, над которыми эти операции производятся. Но не всегда удобно или возможно смешивать операции и данные в одном месте. Логика операции может меняться независимо от самих данных, а вынесение такой логики за пределы иерархии данных чревато дублированием кода и хрупкостью.
В объектно-ориентированном решении базовый класс иерархии задаёт семейство операций, поведение которых определяется наследниками. Таким образом легко добавлять новый тип в иерархию, но сложно добавлять новую операцию (приходится изменять все типы в иерархии). Паттерн «Посетитель» позволяет решить эту проблему. Посетитель позволяет клиентскому коду исследовать иерархию типов и выполнять различные операции в зависимости от конкретного типа объекта.
При этом паттерн «Посетитель» усложняет добавление новых типов в иерархию наследования. Добавление нового типа требует изменения интерфейса посетителя и ломает все его реализации. Это значит, что паттерн «Посетитель» идеально подходит для расширения функциональности стабильных иерархий наследования с переменным числом операций.
Классическая диаграмма приведена на рисунке ниже:
- Visitor — определяет интерфейс посетителя;
- Element — базовый класс иерархии, для которой нужно добавить новую операцию;
- Client — использует посетитель для обработки иерархии элементов.
В базовый класс Element добавляется абстрактный метод Accept, который принимает Visitor, а каждый конкретный класс иерархии просто вызывает метод Visit переданного ему посетителя (в перегруженном методе Accept).

Интерфейс или абстрактный класс посетителя
Обычно посетитель Visitor определяется интерфейсом IVisitor. Такой подход налагает меньше ограничений на клиентов, но делает их более хрупкими. Каждый раз при добавлении типа в иерархию интерфейс посетителя обновляется и в нем появляется новый метод Visit(ConcreteElementC).
Использование абстрактного базового класса VisitorBase позволяет клиентам посещать лишь нужные типы иерархии, переопределяя лишь нужные методы Visit.

Функциональная версия
Когда количество конкретных типов иерархии наследования невелико, интерфейс посетителя можно заменить методом со списком делегатов. Для этого метод Accept можно переименовать в Match, который будет принимать несколько делегатов для обработки конкретных типов иерархии.
public abstract class Element {
public void Match(
Action<ConcreteElementA> handleA,
Action<ConcreteElementB> handleB)
{
switch (this) {
case ConcreteElementA element:
return handleA(element);
case ConcreteElementB element:
return handleB(element);
default:
throw new InvalidOperationException(…);
}
}
}
Вызов:
element.Match(a => HandleA(a), b => HandleB(b));

Применение
- Использовать паттерн «Посетитель» нужно тогда, когда набор типов иерархии стабилен, а набор операций — нет.
- Классический вариант паттерна (с выделением интерфейса) лучше всего подходит для больших составных иерархий и когда заранее не известно, какие типы будут посещаться чаще других.
- Функциональный вариант посетителя всегда можно построить на основе классической реализации, когда станет известно, что многим клиентам нужно посещать лишь небольшое число типов иерархии.

Окончание следует...

Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 6.
День триста тридцать девятый. #DesignPatterns
Паттерны проектирования
6. Паттерн «Посетитель» (Visitor). Окончание
Пример использования паттерна «Посетитель»
Каноническим примером демонстрации полиморфизма является иерархия фигур с базовым классом Shape, имеющим абстрактный метод Area (площадь фигуры), и несколькими потомками, например, Circle, Rectangle и Triangle, каждый из которых переопределяет метод Area. Допустим, что нам понадобилось добавить метод вычисления периметра фигуры. Ранее на канале приводился пример с использованием сопоставления по шаблону.
Рассмотрим, как можно добавлять функциональность, используя паттерн «Посетитель».
1. Интерфейс посетителя:
public interface IShapeVisitor {
void Visit(Circle circle);
void Visit(Triangle square);
void Visit(Rectangle rectangle);
}
2. Добавим в иерархию классов метод Accept, принимающий посетителя, и вызывающий в потомках метод Visit посетителя:
public abstract class Shape {
// … другие методы …
public abstract void Accept(IShapeVisitor visitor);
}
public class Circle : Shape {
// … другие методы …
public override void Accept(IShapeVisitor visitor)
=> visitor.Visit(this);
}
// … аналогично для других потомков
3. Реализуем конкретного посетителя для вычисления периметра:
public class PerimeterVisitor : IShapeVisitor
{
public double Perimeter { get; private set; }
public void Visit(Circle circle) {
Perimeter = 2 * Math.PI * circle.Radius;
}
public void Visit(Rectangle rect) {
Perimeter = 2 * (rect.Height + rect.Width);
}
public void Visit(Triangle tri) {
Perimeter = tri.SideA + tri.SideB + tri.SideC;
}
}
4. Создаём метод расширения:
public static class ShapeExtentions {
public static double Perimeter(this Shape shape) {
var visitor = new PerimeterVisitor();
shape.Accept(visitor);
return visitor.Perimeter;
}
}
// Вызываем
Shape shape = … //получаем форму
var p = shape.Perimeter();
Таким образом для любой новой функции (например, рисования фигуры) нам достаточно выполнить пункты 3 и 4:
- создать конкретный класс посетителя (DrawVisitor);
- создать и вызвать соответствующий метод расширения.
Понятно, что для такого простого примера единого метода расширения с сопоставлением по шаблону, вероятно, будет достаточно. Однако, если добавляемый функционал сложен и объёмен, реализация его в отдельном классе посетителя с разбиением на методы под каждый тип объекта будет предпочтительнее.

Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 18.
День триста сороковой. #Оффтоп
Ещё одно ненапряжное видео. Абсолютно гениальный доклад Дилана Битти “The Art of Code” на конференции Build Stuff 2019. Философские рассуждения на тему, является ли программирование искусством. С кучей разнообразных примеров, красивой презентацией и тонким английским юмором. Я бы сказал, вот так нужно завлекать людей в программирование. Знаете, что такое квайн? Или что есть программа на языке Chef, которая в то же время является рецептом шоколадного пирога? А что изображено на картинке?
Располагайтесь поудобнее и насладитесь искусством программирования: https://youtu.be/gdSlcxxYAA8

Эээм… уже неловко об этом писать, конечно, но требуется хорошее знание английского.
День триста сорок первый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
17. Комментируйте только то, что код не может выразить
Разница между теорией и практикой гораздо очевиднее на практике, чем в теории – это как нельзя лучше относится к комментариям. Теоретически идея комментирования кода звучит убедительно: предложить читателю подробности, объяснение того, что происходит в коде. Что может быть полезнее, чем полезная добавка? Однако на практике комментарии часто становятся проблемой. Как и в любой другой форме письма, нужно уметь писать хорошие комментарии. В основном это умение заключается в том, чтобы знать, когда их не писать.

Когда код некорректен, компиляторы, интерпретаторы и другие инструменты обязательно найдут ошибку. Если код каким-либо образом некорректен в функциональном отношении, обзоры кода, статический анализ, тесты и повседневное использование в производственной среде устранят большинство ошибок. Но что насчет комментариев? В книге “The Elements of Programming Style” Керниган и Плаугер отмечают, что «комментарий имеет нулевую (или отрицательную) ценность, если он неправильный». И всё же такие комментарии часто засоряют код и остаются там даже дольше, чем ошибки. Они являются постоянной причиной отвлечения и источником дезинформации - маленьким, но препятствием в работе программиста.

Что можно сказать о комментариях, которые не являются технически неправильными, но не добавляют никакой ценности к коду? Такие комментарии являются шумом. Комментарии, которые повторяют код, не добавляют ничего полезного для читателя: сообщение о чём-либо один раз в коде и ещё раз на естественном языке не делает код более правильным или более красивым. Закомментированный код не выполняется, поэтому он не несёт никакой пользы ни для читателя, ни для среды исполнения. Такие комментарии также очень быстро устаревают. Комментарии, связанные с версией программы и закомментированный код пытаются рассказать о версиях и истории программы. На эти вопросы уже отвечают (гораздо более эффективно) инструменты контроля версий.

Большое количество бесполезных и неправильных комментариев в коде побуждает программистов игнорировать вообще все комментарии, либо пропуская их, либо принимая активные меры, чтобы их скрыть. Программисты находчивы и будут обходить всё, что воспринимается ими как помеха: скрывать комментарии, изменять цветовую схему так, чтобы комментарии и фон были одного цвета, создавать скрипты для фильтрации комментариев и т.п. Чтобы уберечь код от таких неправильных применений изобретательности программиста и уменьшить риск игнорирования комментариев, имеющих подлинную ценность, комментарии должны рассматриваться как код. Каждый комментарий должен приносить пользу читателю, иначе это мусор, который следует удалить или переписать.

Что же квалифицируется как ценность? Комментарии должны сказать что-то, что код не может выразить. Комментарий, объясняющий то, о чём уже должен говорить блок кода, свидетельствует о том, что нужно изменить структуру кода или соглашения о кодировании, чтобы код говорил сам за себя. Вместо того, чтобы объяснять плохие имена методов или классов, переименуйте их. Вместо того, чтобы комментировать отдельные блоки в длинной функции, разбейте функцию на меньшие по размеру, чьи имена отражают намерения этих блоков. Попробуйте выразить как можно больше через код. Каждое место, которое нельзя выразить в коде так же хорошо, как естественным языком, становится кандидатом на добавление полезного комментария. Комментируйте лишь то, что код не может сообщить в принципе, а не объясняйте то, что он не сообщает сейчас.

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Kelvin Henney