.NET Разработчик
6.5K 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
День двести сорок первый. #ЗаметкиНаПолях
Tracepoints: отладка без помех. Начало
Вы используете отладочный вывод? Давайте будем честными, мы все когда-либо делали это. Будь то Debug.WriteLine(), console.log(), print() и т. д. Вывод данных в консоль - обычная практика. А вы когда-нибудь забывали удалить отладочный вывод из живого кода? И вдруг то, что кажется простым и приятным подходом к отладке, превращается в большую работу по очистке кода. Кроме того, если отладочный вывод используется в нескольких местах, его периодически приходится удалять, чтобы не загромождать консоль.
Если это так, то Tracepoints (точки трассировки) - отличный инструмент, который вы можете использовать в Visual Studio. Эта функция позволяет регистрировать нужную информацию без изменения кода и инициализируется аналогично точкам останова. Когда вы закончите отладку, просто нажмите на точку трассировки, чтобы удалить ее.
Допустим, мы хотим посмотреть значение counter для каждой итерации цикла for (см. картинку ниже). Одним из решений является использование оператора отладки, такого как Debug.WriteLine(counter). В то время как это, безусловно, решает эту простую задачу, нам потребовалось изменить наш код а позже потребуется удалить эту строку, чтобы она не попала в продакшн.
С помощью точек трассировки вы можете делать это, не изменяя код. Обратите внимание, что, когда вы добавляете сообщение в поле «Show a message in the Output window field» в меню Actions (см. картинку), вы никак не изменяете свой исходный код. Это позволяет вам получать необходимую информацию в окне вывода Visual Studio, без ущерба для читабельности вашего кода. Кроме того, когда вы закончите отладку, просто нажмите на точку трассировки один раз, чтобы удалить её. Но даже если вы забыли это сделать, не стоит беспокоиться о постороннем выводе, потому что точки трассировки существуют только локально на вашем компьютере.

Продолжение следует…

Источник:
https://devblogs.microsoft.com/visualstudio/tracepoints/
День двести сорок второй. #ЗаметкиНаПолях
Tracepoints: отладка без помех. Окончание
Допустим, мы хотим выводить только чётные значения счётчика или значения только во время определённой итерации цикла. В блоке Conditions мы можем добавлять условия аналогично условным точкам останова.
Есть три типа условий:
1. Условное выражение (Conditional Expression): сообщение отображается только при определенных условиях, например, counter == 5.
2. Счетчик выполнений (Hit Count): это условие позволяет вам выводить данные только после того, как эта строка кода выполнится заданное количество раз.
3. Фильтр (Filter): точка трассировки будет активирована только на указанных устройствах, процессах или потоках.
Добавление этих условий не изменяет ваш исходный код и, в отличие от точек останова, не останавливает программу и не потребует от вас многократного перехода из программы в отладку и обратно (если установлен флажок «Continue code» в блоке Actions).

Советы
1. Сообщения трассировки отправляются в окно Output. Их легко потерять среди множества других вещей, которые отправляются в это же окно. Если щелкнуть правой кнопкой мыши в окне Output, вы можете отключать типы сообщений, такие как сообщения об исключениях, сообщения о выходе из процесса и т. д. Так вам будет легче сосредоточиться на выводе вашей точки трассировки.
2. Если ваша текущая задача требует наличия всех сообщений, ещё один способ облегчить поиск выходных данных - это поставить уникальный префикс сообщения перед выводом данных, например, MyOutput: {counter}. При отладке вы можете использовать команду поиска Ctrl+F в окне Output, чтобы найти префикс, который вы установили, и окно отобразит для вас нужный вывод.
3. Чтобы временно отключить точку трассировки без её удаления, щёлкните по ней, удерживая Shift.
4. Для одновременного просмотра, отключения или удаления всех точек трассировки и точек останова в текущем файле перейдите в Debug -> Windows -> Breakpoints, чтобы открыть окно точек останова.

В некоторых случаях оператор отладки, такой как Debug.WriteLine(), может быть предпочтительнее точек трассировки. Например, если вы хотите всегда видеть какой-либо вывод в отладчике, который сохраняется за пределами текущего сеанса отладки. Кроме того, точки трассировки менее эффективны. И наконец, точки трассировки имеют ограничения в отношении того, какие данные они могут собирать, поскольку они могут только виртуально оценивать значения функций. Несмотря на некоторые из этих ограничений, точки трассировки являются отличным инструментом в вашем наборе средств отладки.

Дополнительная информация про точки трассировки находится в документации по Visual Studio: https://docs.microsoft.com/en-us/visualstudio/debugger/using-tracepoints?view=vs-2019

Источник: https://devblogs.microsoft.com/visualstudio/tracepoints/
День двести сорок третий. #BestPractices
Разработка для Расширяемости
Одним из важных аспектов проектирования приложения является тщательное планирование его расширяемости. Для этого необходимо понимать затраты и выгоды, связанные с различными механизмами расширяемости.
Есть много способов обеспечить расширяемость приложения. Они варьируются от менее мощных, но менее дорогих до очень мощных, но дорогих. Для любого заданного требования расширяемости вы должны выбирать наименее дорогостоящий механизм, соответствующий требованиям.

Незапечатанные классы
Запечатанные (sealed) классы не могут быть унаследованы, и они препятствуют расширяемости. Напротив, классы, которые могут быть унаследованы, называются незапечатанными классами.
⚠️ РАССМОТРИТЕ использование открытых классов без добавления виртуальных или защищенных членов в качестве способа обеспечить недорогую, но очень ценную расширяемость.
Разработчики часто хотят наследовать от незапечатанных классов, чтобы добавить вспомогательные элементы, такие как пользовательские конструкторы, новые методы или перегрузки методов. Например, System.Messaging.MessageQueue является незапечатанным и, таким образом, позволяет пользователям создавать настраиваемые очереди по умолчанию для определённого пути очереди или добавлять настраиваемые методы, которые упрощают API для конкретных сценариев.
Классы по умолчанию незапечатаны в большинстве языков программирования, и это также рекомендованное поведение по умолчанию для большинства классов. Расширяемость, предоставляемая незапечатанными типами, высоко ценится пользователями фреймворков и довольно недорогая для обеспечения из-за относительно низких затрат на тестирование, связанных с незапечатанными типами.

Продолжение следует...

Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
День двести сорок четвёртый. #BestPractices
Разработка для Расширяемости
Защищённые Члены
Защищенные (protected) члены доступны в пределах своего класса или из экземпляров производного класса. Защищённые члены сами по себе не обеспечивают расширяемости, однако они усиливают расширяемость через подклассы. Их можно использовать для предоставления расширенных возможностей без ненужного усложнения основного публичного интерфейса.
Разработчики фреймворков должны быть осторожны с защищёнными членами, потому что название «защищённый» может дать ложное чувство безопасности. Кто угодно может создать подкласс незапечатанного класса и получить доступ к защищённым членам, поэтому все методы защитного программирования, которые используются для открытых членов, применимы к защищённым членам.
⚠️ РАССМОТРИТЕ использование защищённых членов для расширенной настройки типов.
❗️ НЕОБХОДИМО относиться к защищённым членам в открытых классах как к открытым при анализе безопасности, совместимости или документировании. Кто угодно может наследовать от класса и получить доступ к защищённым членам.

Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
День двести сорок пятый. #BestPractices
Разработка для Расширяемости
События и Функции обратного вызова
Функции обратного вызова - это точки расширения, которые позволяют платформе вызывать пользовательский код через делегата. Эти делегаты обычно передаются в структуру через параметр метода.
События - это особый случай обратных вызовов, который поддерживает удобный и согласованный синтаксис для предоставления делегата (обработчик события). Кроме того, разработчики помогают в этом, предоставляя API на основе событий.
⚠️ РАССМОТРИТЕ использование обратных вызовов, чтобы позволить пользователям выполнять свой код на определённых этапах работы фреймворка.
⚠️ РАССМОТРИТЕ использование событий, чтобы позволить пользователям настраивать поведение фреймворка без необходимости понимания объектно-ориентированного проектирования.
ИСПОЛЬЗУЙТЕ события вместо обратных вызовов, где это возможно, потому что они знакомы более широкому кругу разработчиков.
ИЗБЕГАЙТЕ использования обратных вызовов в чувствительных к производительности API.
ИСПОЛЬЗУЙТЕ типы Func<…>, Action<…> или Expression<…> вместо пользовательских типов делегатов при определении API с обратными вызовами. Func<…> и Action<…> представляют обобщённые делегаты. Expression<…> представляет определения функций, которые могут быть скомпилированы и впоследствии вызваны во время выполнения, но также могут быть сериализованы и переданы удаленным процессам.
ИСПОЛЬЗУЙТЕ замеры производительности, чтобы понимать последствия использования Expression<…> вместо делегатов Func<…> и Action<…>. Типы Expression<…> в большинстве случаев логически эквивалентны делегатам Func<…> и Action<…>. Основное различие между ними заключается в том, что делегаты предназначены для использования в сценариях локальных процессов, а выражения предназначены для случаев, когда необходимо произвести оценку выражения в удаленном процессе или машине.
❗️ ЗАМЕТЬТЕ, что, вызывая делегата, вы выполняете произвольный код, что может иметь последствия для безопасности, корректности и совместимости.

Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
День двести сорок шестой. #BestPractices
Разработка для Расширяемости
Виртуальные Члены
Виртуальные члены могут быть переопределены, тем самым изменяя поведение подкласса. Они очень похожи на обратные вызовы с точки зрения расширяемости, которую они предоставляют, но они лучше с точки зрения производительности и потребления памяти. Кроме того, виртуальные члены более естественны в сценариях, которые требуют создания особого вида существующего типа (специализация). Виртуальные члены работают лучше, чем обратные вызовы и события, но хуже, чем невиртуальные методы.
Основным недостатком виртуальных членов является то, что поведение виртуального члена может быть изменено только во время компиляции. Поведение же метода обратного вызова может быть изменено во время выполнения.
Виртуальные члены, также как и обратные вызовы, являются дорогостоящими для разработки, тестирования и обслуживания, поскольку любой вызов виртуального члена может быть переопределен непредсказуемым образом и может выполнить произвольный код. Кроме того, обычно требуется больше усилий для чёткого определения контракта виртуальных членов, поэтому стоимость их разработки и документирования выше.

ИЗБЕГАЙТЕ создания виртуальных членов, если у вас нет веских причин для этого, и вы не просчитали все расходы, связанные с проектированием, тестированием и обслуживанием виртуальных членов. Виртуальные члены менее защищены от изменений, которые могут быть внесены в них без нарушения совместимости. Кроме того, они медленнее, чем невиртуальные, в основном потому, что компилятор не может оптимизировать код, встроив вызов виртуального члена.
ИСПОЛЬЗУЙТЕ расширяемость только для тех членов, которым она абсолютно необходима.
ИСПОЛЬЗУЙТЕ защищённый (protected) модификатор доступа для виртуальных членов. Открытые члены должны обеспечивать расширяемость (если она требуется), вызывая защищённый виртуальный член. Открытые члены класса должны предоставлять правильный набор функциональных возможностей для прямых потребителей этого класса. Виртуальные члены предназначены для переопределения в подклассах, и защищённый доступ к ним является отличным способом для предоставления потребителям типа возможности для расширения.

Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
День двести сорок седьмой. #BestPractices
Разработка для Расширяемости
Абстракции (Абстрактные типы и интерфейсы)
Абстракция - это тип, который описывает контракт, но не обеспечивает полную реализацию этого контракта. Абстракции обычно реализуются в виде абстрактных классов или интерфейсов и содержат чёткую справочную документацию, описывающую необходимую семантику типов, реализующих контракт. Некоторые из наиболее важных абстракций в .NET Framework включают Stream, IEnumerable<T> и Object.
Вы можете расширять фреймворки, реализовав конкретный тип, который поддерживает контракт абстракции, и используя этот конкретный тип с API-интерфейсами фреймворка, работающими с использованием абстракции.
Содержательную и полезную абстракцию, способную выдержать испытание временем, очень сложно спроектировать. Основная трудность - создать правильный набор членов, не больше и не меньше. Если абстракция имеет слишком много членов, она становится трудной или даже невозможной для реализации. Если у неё слишком мало членов для обещанной функциональности, она становится бесполезной во многих нужных сценариях.
Слишком большое количество абстракций в фреймворке также негативно влияет на удобство использования фреймворка. Часто довольно трудно понять абстракцию, не понимая, как она вписывается в общую картину конкретных реализаций и API, работающих с абстракцией. Кроме того, названия абстракций и их члены обязательно являются слишком общими, что часто делает их непонятными без предварительного глубокого разбора контекста их использования.
Однако абстракции обеспечивают чрезвычайно мощную расширяемость, которую другие механизмы расширяемости часто не могут предоставить. Они лежат в основе многих архитектурных шаблонов, таких как подключаемые модули, инверсия управления (IoC), конвейеры и т. д. Они также чрезвычайно важны для тестируемости фреймворков. Хорошие абстракции позволяют избавиться от тяжелых зависимостей для проведения модульного тестирования. Таким образом абстракции отвечают за богатство современных объектно-ориентированных структур.

ИЗБЕГАЙТЕ предоставления абстракций, если они не протестированы путем разработки нескольких конкретных реализаций и API, использующих эти абстракции.
НЕОБХОДИМО выбрать между абстрактным классом и интерфейсом при разработке абстракции.
⚠️ РАССМОТРИТЕ предоставление справочных тестов для конкретных реализаций абстракций. Такие тесты должны позволять пользователям проверять, правильно ли их реализации реализуют контракт.

Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
День двести сорок восьмой. #BestPractices
Разработка для Расширяемости
Базовые классы для реализации абстракций
Строго говоря, класс становится базовым, когда другой класс наследует от него. Однако здесь подразумеваем, что базовый класс - это класс, предназначенный главным образом для обеспечения обобщённой абстракции или для повторного использования некоторой стандартной функциональности другими классами через наследование. Базовые классы обычно находятся в середине иерархий наследования, между абстракцией в корне иерархии и несколькими пользовательскими реализациями внизу.
Они служат помощниками для реализации абстракций. Например, одной из абстракций .Net Framework для упорядоченных коллекций элементов является интерфейс IList<T>. Реализация IList<T> не тривиальна, и поэтому фреймворк предоставляет несколько базовых классов, таких как Collection<T> и KeyedCollection<TKey, TItem>, которые служат помощниками для реализации пользовательских коллекций.
Базовые классы обычно не подходят для использования в качестве абстракций, потому что они, как правило, содержат слишком много реализованной функциональности. Например, базовый класс Collection<T> содержит множество реализаций, например, необобщённого интерфейса IList (для лучшей интеграции с необобщёнными коллекциями), а также функционал управления коллекцией элементов, хранящихся в памяти в одном из полей класса.
Как уже говорилось ранее, базовые классы могут оказать неоценимую помощь пользователям, которым необходимо реализовывать абстракции, но в то же время они могут стать серьезной проблемой. Они увеличивают глубину иерархии наследования и таким образом усложняют структуру. Поэтому базовые классы следует использовать только в том случае, если они обеспечивают значительную ценность для пользователей фреймворка. Их следует избегать, если они обеспечивают ценность только для разработчиков фреймворка, и в этом случае настоятельно рекомендуется делегировать внутреннюю реализацию вместо наследования от базового класса.

⚠️ РАССМОТРИТЕ возможность сделать базовые классы абстрактными, даже если они не содержат абстрактных членов. Это ясно сообщает пользователям, что класс предназначен исключительно для наследования.
⚠️ РАССМОТРИТЕ возможность размещения базовых классов в пространстве имен отдельном от основных вариантов использования. По определению базовые классы предназначены для сценариев расширяемости, что является продвинутым уровнем использования фреймворка, и поэтому не интересно большинству пользователей.
ИЗБЕГАЙТЕ именования базовых классов с суффиксом «Base», если этот класс предназначен для использования в общедоступных API.

Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
День двести сорок девятый. #BestPractices
Разработка для Расширяемости
Запечатывание
Одной из особенностей объектно-ориентированных фреймворков является то, что пользователи могут расширять и изменять их способами, не предусмотренными разработчиками фреймворка. Это и сила, и опасность расширяемого дизайна. Поэтому, когда вы разрабатываете свой фреймворк, очень важно тщательно продумать возможности расширения, где это необходимо, и ограничить возможности расширения, где оно опасно.
Мощный механизм, препятствующий расширению – это запечатывание (sealing). Вы можете запечатать класс или отдельные его члены. Запечатывание класса препятствует наследованию от класса. Запечатывание члена предотвращает переопределение пользователями отдельного члена.

ИЗБЕГАЙТЕ запечатывания класса, не имея на то веских причин. Запечатывание класса только по причине того, что вы не можете придумать сценарии его расширяемости, - это плохая идея. Пользователи фреймворков любят наследовать от классов по разным неочевидным причинам, например, добавляя удобные члены, такие как пользовательские конструкторы, новые методы или перегрузки методов. Например, System.Messaging.MessageQueue является незапечатанным и, таким образом, позволяет пользователям создавать настраиваемые очереди, имеющие путь по умолчанию, или добавлять настраиваемые методы, которые упрощают API в определенных сценариях.

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

Хорошие причины для запечатывания класса:
- Класс является статическим классом (см. https://t.iss.one/NetDeveloperDiary/241)
- Класс хранит чувствительный к безопасности код в унаследованных защищенных членах.
- Класс наследует много виртуальных членов, и стоимость запечатывания которых по одиночке превышает преимущества от оставления класса незапечатанным.
- Класс является атрибутом, который требуется очень быстро находить во время выполнения. Запечатанные атрибуты имеют немного более высокую производительность, чем незапечатанные.

ИЗБЕГАЙТЕ объявления защищённых или виртуальных членов в запечатанных типах. По определению от запечатанных типов нельзя унаследовать. Это означает, что защищённые члены запечатанных типов не могут быть вызваны, а виртуальные методы запечатанных типов не могут быть переопределены.
⚠️ РАССМОТРИТЕ возможность запечатать члены класса, которые вы переопределяете. Проблемы, которые могут возникнуть в результате введения виртуальных членов (см. https://t.iss.one/NetDeveloperDiary/286), также применимы к переопределениям, хотя и в несколько меньшей степени. Запечатывание переопределения защищает вас от этих проблем, начиная с этой точки в иерархии наследования.

Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
День двести пятидесятый. #ЗаметкиНаПолях
Использование методов расширения в инициализаторах коллекций
Для использования инициализатора коллекции необходимы два условия:
- Тип должен реализовывать IEnumerable. Это раздражающее ограничение. Иногда приходится реализовывать IEnumerable исключительно для того, чтобы использовать тип в инициализаторах коллекций.
- Должен быть подходящий метод Add для каждого элемента в инициализаторе коллекции. Предполагается, что любые элементы, которые не находятся в фигурных скобках, соответствуют вызовам метода Add с одним аргументом. Когда требуется несколько аргументов, они должны быть в фигурных скобках. Например:
List<string> strings = new List<string> { 10, "hello", { 20, 3 } };
Эквивалентно:
List<string> strings = new List<string>();
strings.Add(10);
strings.Add("hello");
strings.Add(20, 3);
Далее для определения, что значит каждый вызов, применяется стандартное разрешение перегрузки методов. С обычным List<T> этот код не скомпилируется. Но можно добавить метод расширения:
public static void Add(
this List<string> list, int value, int count = 1)
{
list.AddRange(Enumerable.Repeat(value.ToString(), count));
}
Этот метод добавляет значение первого аргумента столько раз, сколько указано во втором аргументе. Таким образом список примет вид: "10", "hello", "20", "20", "20".

Варианты использования:
1. Создание сигнатур метода Add общего назначения. Например, инициализация коллекцией:
public static void Add<T>(this List<T> list, IEnumerable<T> collection)
{
list.AddRange(collection);
}
Применение (контакты человека инициализируются коллекцией людей из Москвы):
Person jon = new Person
{
Name = "Jon",
Contacts = { allContacts.Where(c => c.Town == "Moscow") }
};
2. Создание специализированных сигнатур метода Add. Например, добавление в словарь объектов (людей) с ключом в виде одного из свойств (имя):
public static void Add(
this Dictionary<string, Person> dictionary, Person person)
{
dictionary.Add(person.Name, person);
}
Применение:
var dictionary = new Dictionary<string, Person>
{
{ new Person { Name = "Jon", … } },
{ new Person { Name = "Holly", … } }
};

Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 10.
День двести пятьдесят первый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
1. Действуйте с осторожностью

Что бы вы ни предпринимали, действуйте с осторожностью и учитывайте последствия.
- Анон

Неважно, насколько приятно выглядит график разработки в начале итерации, вы не сможете избежать работы под давлением от случая к случаю. Если вам приходится выбирать между «сделать правильно» и «сделать быстро», часто бывает соблазнительно «сделать быстро», подразумевая, что вы вернётесь и исправите это позже. Когда вы обещаете это себе, своей команде или своему клиенту, вы действительно намерены это исправить. Но слишком часто следующая итерация приносит новые проблемы, и вы сосредотачиваетесь на них. Этот вид отложенной работы известен как технический долг, и это ваш враг. В частности, Мартин Фаулер называет это преднамеренным техническим долгом, и его не следует путать с непреднамеренным.
Технический долг подобен займу: вы получаете выгоду от него в краткосрочной перспективе, но должны платить по нему проценты, пока он не будет полностью погашен. Костыли в коде затрудняют добавление функций или рефакторинг. Они являются питательной средой для ошибок и недостаточно полных тестов. Чем дольше вы их оставляете, тем хуже становится. К тому времени, когда вы приступите к выполнению первоначального исправления, может появиться целый набор не совсем правильных вариантов дизайна, наслоенных поверх исходной проблемы, что сделает код намного сложнее для рефакторинга и исправления. На самом деле, зачастую вы возвращаетесь к исправлению изначальной проблемы только тогда, когда всё становится настолько плохо, что вам не остаётся ничего другого. А к этому моменту это уже настолько трудно сделать, что вы в реальности не можете позволить себе потратить столько времени или пойти на такой риск.
Есть моменты, когда вы должны взять на себя технические долги, чтобы уложиться в срок или реализовать небольшой фрагмент функциональности. Постарайтесь не оказываться в таком положении, но, если ситуация этого требует, действуйте. Но (и это значительное «но») вы должны отслеживать технические долги и быстро их погашать, или дела быстро пойдут вниз. Как только вы примете решение пойти на компромисс, создайте задачу в своей системе отслеживания ошибок, чтобы не забыть это исправить.
Если вы планируете погашение долга на следующей итерации, стоимость будет минимальной. Оставляя долг неоплаченным, вы попадаете на проценты, и за ними нужно следить, чтобы знать цену исправления. Это позволит надлежащим образом расставлять приоритеты задач. Выбор того, как рассчитывать и отслеживать стоимость исправления технического долга, будет зависеть от конкретного проекта, но отслеживать её необходимо.

Погашайте технический долг как можно быстрее.

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
День двести пятьдесят второй. #юмор
Ещё немного тонкого английского юмора)))
День двести пятьдесят третий. #ЗаметкиНаПолях
Фильтры исключений
Представьте, что вы выполняете веб-операцию и знаете, что сервер, к которому вы подключаетесь, иногда недоступен. Если вам не удаётся подключиться к нему, то у вас есть запасной вариант, однако любой другой вид сбоя должен привести к обычному выбросу исключения. До C #6 нужно было поймать исключение и выбросить его повторно, если статус отличался от нужного:
try {…}
catch (WebException e)
{
if (e.Status != WebExceptionStatus.ConnectFailure)
{
throw;
}

}
Используя фильтры исключений, если вы не хотите обрабатывать исключение, можно не перехватывать его, а отфильтровать сразу в блоке catch:
try {…}
catch (WebException e)
when (e.Status == WebExceptionStatus.ConnectFailure) {…}

Варианты использования:
1. Перехват одного исключения несколько раз
В прошлом всегда было ошибкой указывать один и тот же тип исключения в нескольких блоках catch для одного и того же блока try. Это не имело смысла, потому что второй блок никогда не достигался. С фильтрами исключений ситуация изменилась. Предположим, вы извлекаете веб-контент на основе URL-адреса, предоставленного пользователем. Возможно, вы захотите обработать сбой соединения одним способом, сбой разрешения домена другим способом и не обрабатывать другие типы сбоев:
try {…}
catch (WebException e)
when (e.Status == WebExceptionStatus.ConnectFailure) {…}
catch (WebException e)
when (e.Status == WebExceptionStatus.NameResolutionFailure) {…}
Если вы хотите обработать все другие исключения WebException на том же уровне, можно добавить общий блок catch (WebException e) {…} без фильтра последним пунктом.
2. Повторные попытки
Облачные вычисления становятся все более распространенными, и мы, как правило, всё больше узнаём об операциях, которые могут привести к сбою, и о задумываемся о том, как обрабатывать сбои в нашем коде. При удаленных операциях - например, вызовах удалённых веб-служб и баз данных - иногда возникают временные сбои, и можно безопасно повторить попытку. Вот как это можно реализовать, используя фильтры исключений:
static T Retry<T>(Func<T> operation, int attempts)
{
while (true)
{
try
{
attempts--;
return operation();
}
catch (Exception e) when (attempts > 0)
{
Console.WriteLine($"Неудача: {e}");
Console.WriteLine($"Осталось попыток: {attempts}");
Thread.Sleep(5000);
}
}
}
3. Запись в лог, как побочный эффект
Иногда полезно записывать исключение в лог, даже если оно будет перехвачено на более верхних уровнях. Вы можете использовать для этого фильтры исключений, чтобы не нарушать поток выполнения. Всё, что вам нужно, это фильтр исключений, который вызывает метод для записи в лог и возвращает false, чтобы указать, что вы не хотите перехватывать это исключение:
try {…}
catch (Exception e) when (Log(e)) {…}

static bool Log(Exception e)
{
Console.WriteLine($"{DateTime.UtcNow}: {e.GetType()} {e.Message}");
return false;
}

Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 10.
День двести пятьдесят четвёртый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
2. Применяйте принципы функционального программирования
Не так давно в сообществе программистов стал возрождаться интерес к функциональному программированию. Одна из причин – то, что
функциональная парадигма упрощает использование многоядерности, к которой движется современная ИТ индустрия. Овладение парадигмой функционального программирования может значительно улучшить качество кода, написанного в других контекстах. Если вы глубоко понимаете и применяете функциональную парадигму, в вашем коде будут преобладать чистые (детерминированные) функции.
Чистые функции - очень желательное свойство кода. Оно подразумевает, что функции дают одинаковые результаты при одинаковых входных данных, независимо от того, где и когда они вызываются. Таким образом, выполнение функции мало зависит (а в идеале, совсем не зависит) от изменяемого состояния приложения.
Основная причина дефектов в императивном коде связана с изменяемыми переменными. Наверняка каждый из вас не раз искал причину, почему значение какой-либо переменной не такое, как ожидалось в определённой ситуации. Управление областью видимости может помочь смягчить эти коварные дефекты или, по крайней мере, радикально сузить район поиска источника проблем, но их истинным виновником на самом деле может быть использование конструкций, в которых используется чрезмерная изменяемость.
И мы, к сожалению, не видим в этом помощи от индустрии. ООП неявно продвигает подобный подход к дизайну, предлагая примеры систем объектов, вовсю вызывающих методы-мутаторы друг друга. Однако при помощи разработки через тестирование можно избавиться от ненужной изменяемости.
Конечным результатом является дизайн, который обычно имеет лучшее распределение ответственности с более многочисленными, но меньшими по объёму функциями, которые действуют на передаваемые в них аргументы, а не вызывают методы, изменяющие состояние объекта. Ошибок будет меньше, кроме того, их часто будет проще отлаживать, поскольку легче определить, где в цепочку функций попало неверное значение, чем каким-либо иным образом обнаружить конкретный контекст, который приводит к ошибочному присваиванию. Это обеспечивает гораздо более высокую чистоту функций. И, безусловно, ничто не сможет так научить вас этому, как изучение функционального языка программирования, где эта модель вычислений является нормой.
Конечно, такой подход не может быть оптимальным во всех ситуациях. Например, в объектно-ориентированных системах этот стиль обычно приносит гораздо лучшие результаты при разработке бизнес-логики, чем при разработке пользовательского интерфейса.
Овладейте парадигмой функционального программирования, чтобы вы могли разумно применять полученные уроки в других областях. Ваши объектно-ориентированные системы получат огромную пользу от чистых функций и могут стать намного ближе к своим функциональным аналогам, чем вы думаете.

Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
День двести пятьдесят пятый #Оффтоп
Давал уже ссылку на этот канал, но не могу не поделиться их новым видео. Ребята просто шикарны. ExtremeCode про парадигмы ООП https://www.youtube.com/watch?v=BHNt1fcg8iw Смотреть пришлось два раза, потому что с первого раза так ржал, что мало что понял. Осторожно, 18+
День двести пятьдесят шестой.
Сертификат Microsoft. Шаг 3
Прошло много времени с тех пор, как в последний раз писал что-то по этой теме. Итак, запланировал сдачу на ноябрь. Точную дату пока не выбрал. Экзамен можно сдать онлайн из дома. Нужна рабочая веб-камера и стабильное интернет-соединение. Там ещё куча ограничений по поводу того, что на столе ничего не должно быть, в комнате никого не должно быть, кроме вас, не должно быть никаких посторонних звуков. Всё время экзамена за вами будут наблюдать, если наблюдающему покажется, что вам кто-то подсказывает, вы куда-то подсматриваете или происходит ещё что-то подозрительное, экзамен прекращается без оценки, деньги не возвращаются. В общем, всё строго, подробнее опишу ближе к делу.
Пока для подготовки нашёл второе издание книги «Exam Ref 70-483 Programming in C#» https://www.microsoftpressstore.com/store/exam-ref-70-483-programming-in-c-sharp-9781509306985 Всё-таки, первое было от 2013 года, и много воды с тех пор утекло. Начал изучать. Что могу сказать. Эту книгу стоит рассматривать исключительно как материал для подготовки к этому конкретному экзамену. Более того, как руководство для повторения изученного материала. Потому что, во-первых, книга организована в порядке тем, перечисленных на странице экзамена, поэтому сначала идёт тема многопоточности (потоки, задачи, исключения), а потом – внезапно - управление логикой программы (циклы, условные операторы и т.п.), то есть сначала продвинутые программные конструкции, а потом самые базовые. Во-вторых, описано очень сжато и порой сумбурно, а также довольно много опечаток как в тексте, так и в коде. То есть научиться по этой книге не получится (хотя по некоторым темам встречаются рассуждения и подробные объяснения на несколько страниц). А вот для «вспомнить всё» вполне сойдёт.
День двести пятьдесят седьмой. #ЗаметкиНаПолях
Использование потокобезопасных коллекций. Начало
Потокобезопасные элементы работают правильно, когда используются из нескольких потоков (задач) одновременно. Стандартные коллекции .NET (List, Queue или Dictionary) не являются потокобезопасными. Библиотеки .NET предоставляют классы потокобезопасных коллекций, которые можно использовать при создании многозадачных приложений: BlockingCollection<Т>, ConcurrentQueue<Т>, ConcurrentStack<Т>, ConcurrentBag<Т>, ConcurrentDictionary<TKey, TValue>

ConcurrentQueue
Предоставляет потокобезопасную коллекцию, обслуживаемую по принципу "первым поступил — первым обслужен" (FIFO). Метод Enqueue добавляет элементы в очередь, а метод TryDequeue удаляет их. Обратите внимание, что, хотя метод Enqueue гарантированно сработает (очереди могут иметь бесконечную длину), метод TryDequeue вернет false в случае сбоя при извлечении из очереди.
Синхронизация обеспечивается внутри ConcurrentQueue<T>. Если два потока вызывают TryDequeue в один и тот же момент, ни одна из операций не блокируется. При обнаружении конфликта между двумя потоками, один поток должен попытаться снова получить следующий элемент.
TryDequeue пытается удалить элемент из очереди. Это происходит атомарно по отношению к другим операциям в очереди. Если очередь была заполнена элементами a, b и c, и два потока одновременно пытаются удалить из очереди элемент, один поток удалит из очереди a, а другой поток удалит из очереди b. Оба вызова TryDequeue вернут true, потому что они оба смогли удалить элемент из очереди. Если каждый поток попытается извлечь ещё по одному элементу, один из потоков удалит из очереди c и вернёт true, тогда как другой поток найдёт очередь пустой и вернет false.
Третий метод, TryPeek, позволяет программе проверить элемент в начале очереди, не извлекая его. Заметьте, что даже если метод TryPeek возвратит элемент, последующий вызов метода TryDequeue в том же потоке, для извлечения этого элемента из очереди, может завершиться неудачей, если элемент будет извлечён в другом потоке.

ConcurrentStack
Класс ConcurrentStack обеспечивает поддержку потокобезопасных стеков Элементы обслуживаются по принципу «первым пришёл – последним обслужен» (LIFO). Метод Push добавляет элементы в стек, а метод TryPop извлекает их. Существуют также методы PushRange и TryPopRange, которые можно использовать для добавления или извлечения нескольких элементов.

Можно перечислить элементы очереди или стека (программа может использовать конструкцию foreach для работы с каждым элементом в очереди). В начале перечисления параллельная очередь предоставит моментальный снимок содержимого.
Свойство Count возвращает количество элементов в коллекции, а свойство IsEmpty сообщает, является ли коллекция пустой (его предпочтительнее использовать, вместо сравнения Count с 0). Но вследствие многопоточного использования, значения этих свойств могут сразу же терять актуальность из-за действий в других потоках.

ConcurrentBag
Представляет потокобезопасный контейнер неупорядоченной коллекции объектов. Метод Add помещают элементы в коллекцию, а метод TryTake извлекает их. Существует также метод TryPeek, но он менее полезен в ConcurrentBag, поскольку возможно, что последующий метод TryTake вернет другой элемент.
Контейнеры полезны для хранения элементов, когда порядок не имеет значения, и в отличие от наборов, контейнеры поддерживают дублирование элементов. ConcurrentBag<T> оптимизирован для сценариев, в которых один и тот же поток будет как производить, так и потреблять данные, хранящиеся в контейнере. ConcurrentBag<T> может принимать null в качестве допустимого значения для ссылочных типов.

Источники:
- Rob Miles “Exam Ref 70-483 Programming in C#”. 2nd ed - Pearson Education, Inc., 2019. Глава 1.
-
https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent?view=netframework-4.8
👍1
День двести пятьдесят восьмой. #ЗаметкиНаПолях
Использование потокобезопасных коллекций. Продолжение
ConcurrentDictionary
Словарь предоставляет хранилище данных, проиндексированное по ключу. ConcurrentDictionary<TKey, TValue> может использоваться несколькими параллельными задачами. Действия над словарем выполняются атомарно. Другими словами, действие по обновлению элемента в словаре не может быть прервано действием из другой задачи. ConcurrentDictionary предоставляет некоторые дополнительные методы, которые необходимы, когда словарь используется несколькими задачами.
ConcurrentDictionary<string, int> ages = new ConcurrentDictionary<string, int>();
if (ages.TryAdd("Иван", 21))
Console.WriteLine("Иван успешно добавлен.");
Console.WriteLine("Возраст Ивана: {0}", ages["Иван"]);
// Пытаемся изменить возраст с 21 на 22
if (ages.TryUpdate("Иван", 22, 21))
Console.WriteLine("Возраст успешно обновлён");
Console.WriteLine("Новый возраст Ивана: {0}", ages["Иван"]);
// Увеличиваем возраст, используя фабричный метод
Console.WriteLine("Возраст Ивана обновлён до: {0}",
ages.AddOrUpdate("Иван", 1,
(name,age) => age = age+1));
Console.WriteLine("Новый возраст Ивана: {0}", ages["Иван"]);
Метод TryAdd пытается добавить новый элемент. Если элемент уже существует, метод TryAdd возвращает false. Метод TryUpdate поставляется с ключом обновляемого элемента, новым значением, которое должно быть сохранено в элементе, и значением, которое должно быть перезаписано. В приведенном выше примере возраст элемента «Иван», будет обновлен до 22, только если существующее значение равно 21. Это позволяет программе обновлять элемент, только если он имеет ожидаемое значение.
Метод AddOrUpdate позволяет предоставить поведение, которое будет выполнять обновление заданного элемента или добавлять новый элемент, если он ещё не существует. В приведённом выше примере добавление элемента «Иван» не будет выполнено при вызове AddOrUpdate, поскольку элемент уже существует. Вместо этого выполняется пользовательский делегат, передаваемый в качестве третьего аргумента, который увеличивает возраст элемента «Иван» на 1.
Метод GetOrAdd позволяет получить существующее значение для указанного ключа или, если ключ не существует, вы добавить пару ключ/значение. Методу GetOrAdd также можно передать делегат для задания значения.
ConcurrentDictionary предназначен для многопоточных сценариев. Вам не нужно использовать блокировки в своем коде для добавления или удаления элементов из коллекции. Однако один поток всегда может извлечь значение, а другой поток - немедленно обновить коллекцию, задав этому же ключу новое значение.
Кроме того, хотя все методы ConcurrentDictionary являются потокобезопасными, не все методы являются атомарными, в частности GetOrAdd и AddOrUpdate. Пользовательский делегат, который передается этим методам, вызывается вне внутренней блокировки словаря (это делается для того, чтобы неизвестный код не блокировал все потоки). Следовательно, возможна следующая последовательность событий:
1) Поток A вызывает GetOrAdd, не находит элемента и создаёт новый элемент для добавления, вызывая делегат.
2) Поток B одновременно вызывает GetOrAdd, вызывается его делегат, и он достигает внутренней блокировки словаря перед потоком A, поэтому его новая пара ключ-значение добавляется в словарь.
3) Продолжается выполнение делегата из потока A, он достигает блокировки, но теперь видит, что элемент уже существует.
4) Поток A выполняет «Get» и возвращает данные, которые были ранее добавлены потоком B.
Поэтому нет гарантии, что данные, возвращаемые GetOrAdd, являются теми же данными, которые создаются в делегате потока. Подобная последовательность событий может происходить и при вызове AddOrUpdate.

Источники:
- Rob Miles “Exam Ref 70-483 Programming in C#”. 2nd ed - Pearson Education, Inc., 2019. Глава 1.
-
https://docs.microsoft.com/dotnet/standard/collections/thread-safe/how-to-add-and-remove-items
День двести пятьдесят девятый. #ЗаметкиНаПолях
Использование потокобезопасных коллекций. Окончание
BlockingCollection<Т>
С точки зрения разработки лучше всего реализовывать паттерн «производитель/потребитель», то есть рассматривать задачи в многопоточном приложении как либо производителя, либо потребителя данных. Задача, которая и производит, и потребляет данные, уязвима для ситуаций «взаимной блокировки». Если задача A ожидает чего-то, создаваемого задачей B, а задача B ожидает чего-то, создаваемого задачей A, и ни одна из задач не может быть запущена.
Класс BlockingCollection<T> обеспечивает потокобезопасное средство добавления и извлечения элементов из хранилища данных. Он называется блокирующей коллекцией, потому что действие Take блокирует задачу, если нет элементов, которые необходимо извлечь. Попытки добавить элементы в заполненную коллекцию также блокируются. Кроме того, можно установить верхний предел размера коллекции.
Следующий код создаёт поток, который пытается добавить 5 элементов в коллекцию BlockingCollection из 3 элементов. После добавления 3-го элемента этот поток блокируется. Программа также создаёт поток, который извлекает элементы из коллекции. Как только поток чтения начинает работать и извлекает некоторые элементы из коллекции, поток записи может продолжиться:
BlockingCollection<int> data = new BlockingCollection<int>(3);
Task.Run(() =>
{
for(int i=1;i<=5;i++)
{
data.Add(i);
Console.WriteLine("Число {0} добавлено.", i);
}
data.CompleteAdding();
});
Console.ReadKey();
Console.WriteLine("Чтение элементов");
Task.Run(() =>
{
while (!data.IsCompleted)
{
try
{
int v = data.Take();
Console.WriteLine("Число {0} прочитано.", v);
}
catch (InvalidOperationException) { }
}
});
Console.ReadKey();


Пример вывода:
Число 1 добавлено
Число 2 добавлено
Число 3 добавлено
Чтение элементов
Число 1 прочитано
Число 2 прочитано
Число 3 прочитано
Число 4 добавлено
Число 5 добавлено
Число 4 прочитано
Число 5 прочитано

Задача добавления вызывает метод CompleteAdding(), после добавления последнего элемента. Это предотвращает добавление новых элементов в коллекцию. Извлекающая задача использует свойство IsCompleted, чтобы определить, когда прекратить получать элементы. Свойство IsCompleted возвращает true, когда коллекция пуста и вызван CompleteAdding. Операция извлечения выполняется внутри конструкции try-catch, т.к. метод Take может выбросить исключение, если между проверкой IsCompleted и вызовом Take добавляющая задача вызовет CompleteAdding. То есть получающая задача попытается выполнить извлечение из коллекции, которая была помечена как завершённая.
BlockingCollection также предоставляет методы TryAdd и TryTake, которые используются для попытки выполнения действия. Они возвращает true, если действие выполнено успешно. Кроме того, им можно передать максимальное время ожидания завершения действия и токен отмены. Класс BlockingCollection выступает в качестве оболочки для других потокобезопасных классов коллекций, включая ConcurrentQueue (используется по умолчанию), ConcurrentStack и ConcurrentBag.

Источник: Rob Miles “Exam Ref 70-483 Programming in C#”. 2nd ed - Pearson Education, Inc., 2019. Глава 1.
This media is not supported in your browser
VIEW IN TELEGRAM
День двести шестидесятый. #ЧтоНовенького
Вертикальная панель вкладок
В Microsoft объявили о выпуске Visual Studio 2019 version 16.4 Preview 2.
Отображение вкладок в вертикальной панели было одной из самых востребованных функций у сообщества разработчиков. Реализация вертикальной панели вкладок, начиная с Visual Studio 2019 v16.4 Preview 2, является одним из многих шагов, позволяющих значительно улучшить управление документами. Выше приведён краткий пример того, как выглядит новая функция.

Источник: https://devblogs.microsoft.com/visualstudio/fall-sports-pumpkin-spice-and-visual-studio-2019-v16-4-preview-2/