.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
Сколько времени займёт выполнение кода? (см. картинку в первом комментарии)
#Quiz #CSharp #Async
Anonymous Quiz
7%
завершится мгновенно
10%
~1000ms
32%
~2000ms
31%
~3000ms
11%
~4000ms
9%
больше 4000ms
👍15
День 1490. #ЗаметкиНаПолях
Какой Интерфейс Коллекции Использовать? Продолжение
Начало

2. IList и Array
Заметьте, что здесь говорится об интерфейсе IList, а не конкретном типе List. Они почти идентичны с точки зрения функциональности, и поэтому, хотя технически List более конкретен, чем IList, на практике не имеет большого значения, какой из них вы используете.
// вариант 1
public IList<Student> GetAll()

// вариант 2
public Student[] GetAll()
При принятии решения о том, какой тип использовать в качестве возвращаемого значения, мы не только должны быть осторожны с утечкой абстракций, но также должны учитывать, правильно ли этот тип представляет то, что клиент может и не может делать с возвращаемыми данными.

Например, клиентский код может изменить список студентов после того, как получит их из репозитория. Вы можете делать это по двум причинам:
- Вы изменяете коллекцию, чтобы добавить или удалить студентов из БД.
- Вы повторно используете коллекцию для других целей. Например, нужно отфильтровать студентов по некоторым критериям, и вместо создания новой коллекции вы удаляете элементы из существующей коллекции.

Обе эти причины неправильные.

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

Замечание: да, ORM (NHibernate или EF Core) позволяет эмулировать связь объектов с данными в базе, но это не наш случай, когда мы возвращаем коллекцию из метода.

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

Мы также не должны модифицировать коллекцию, чтобы повторно использовать её для каких-то других целей. Если у вас нет серьёзных ограничений производительности (что почти никогда не бывает), нужно просто создать новую коллекцию с отфильтрованными данными. Ненужная модификация данных часто приводит к целому ряду сложно поддающихся отладке проблем, которых можно избежать, просто сделав эти данные неизменяемыми. Поэтому мы должны использовать только неизменяемые типы коллекций как для аргументов, так и для возвращаемых значений.

Таким образом, у нас остались:
- IEnumerable для аргументов как наиболее общий тип из возможных,
- IReadOnlyList для возвращаемых значений как наиболее конкретный возможный тип.

Мы используем IReadOnlyList вместо IEnumerable, потому что IReadOnlyList обеспечивает больше функциональности: свойство Count, а также доступ к коллекции через индекс.

Замечание: IReadOnlyList не обязательно является неизменяемым «под капотом». Конкретный тип List также реализует IReadOnlyList, и теоретически вы можете привести IReadOnlyList обратно к List и изменить его. Но это привязывает ваш код к деталям реализации метода, возвращающего IReadOnlyList, и делает этот код ненадёжным. Клиентский код не должен полагаться ни на какую информацию, кроме предоставленной сигнатурой метода, и не должен делать никаких предположений об объекте, кроме того, что говорит его тип. Другими словами, никакого даункастинга.
Если вам необходимо изменять возвращаемую коллекцию, просто верните IList вместо IReadOnlyList или создайте новую коллекцию на основе имеющегося IReadOnlyList.

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

Источник:
https://enterprisecraftsmanship.com/posts/which-collection-interface-to-use/
👍13
День 1491. #ЗаметкиНаПолях
Какой Интерфейс Коллекции Использовать? Окончание
Начало
Продолжение

3. IEnumerable - тоже дырявая абстракция
В предыдущем посте мы выяснили, что нужно использовать IReadOnlyList в качестве возвращаемого типа коллекции. Но почему так много известных библиотек вместо этого используют IEnumerable?

Одна из причин — ленивая оценка. В .NET BCL большинство инструкций LINQ обрабатываются лениво при работе с IEnumerable:
var numbers = new int[] { 1, 2, 3 };
var r1 = numbers.Where(x => x > 1);
var r2 = r1.Select(x => x + 1);
int result = r2.Sum();

Здесь выполнение 2 и 3 строк откладывается до строки 4, где вызывается Sum. Если нужно поддерживать ленивую оценку, используйте IEnumerable, иначе IReadOnlyList.

IEnumerable и ORM
Обратите внимание, что ленивое вычисление также приводит к тому, что IEnumerable в некоторых сценариях является дырявой абстракцией:
// Student repository
public IEnumerable<Student> GetAll()
{
using SchoolContext ctx = new();
return ctx.Set<Student>();
}

// контроллер
public IEnumerable<StudentDto> Get()
{
var students = _repo.GetAll();
var dtos = students.Select(MapToDto);
return dtod.ToList();
}

Код в контроллере выполняется только когда вызывается ToList. Если к этому моменту DbContext уже утилизирован, мы получим исключение. Это ещё один пример дырявой абстракции: EF Core требует от вас знать, открыто ли соединение с БД во время оценки IEnumerable. Хотя это не такая уж проблема, поскольку наличие постоянного DbContext является разумным предположением в большинстве веб-приложений. У вас почти всегда будет активный экземпляр DbContext на протяжении всей бизнес-операции.

IEnumerable без ORM
Реализации IEnumerable часто действуют как дырявые абстракции даже в сценариях без ORM. Например, BlockingCollection реализует IEnumerable таким образом, что он блокирует вызывающий поток в методе MoveNext() до тех пор, пока другой поток не добавит элемент в эту коллекцию. А при реализации «бесконечной» коллекции через yield return вызов ToList на такой коллекции приведёт к зависанию программы в бесконечном цикле. Технически это не дырявая абстракция, т.е. IEnumerable не обещает конечность коллекции, но на практике большинство программистов ожидают от IEnumerable определённого поведения. Можно сказать, что IEnumerable имеет неявный контракт, на который полагаются большинство программистов, и некоторые реализации IEnumerable нарушают его.

IEnumerable в разработке библиотек
Если вы разрабатываете библиотеку для публичного использования, лучше максимально ограничить её публичный API, чтобы дать себе возможность обновлять его. Это включает как количество публичных методов, так и типы, возвращаемые этими методами. Тогда предпочтительнее возвращать IEnumerable вместо IReadOnlyList. Это позволит изменить внутреннюю реализацию, например, List<T> на LinkedList<T>, не нарушая обратной совместимости.

Итого
- Используйте IEnumerable для аргументов как наиболее универсальный тип из возможных.
- Используйте IReadOnlyList для возвращаемых значений как наиболее конкретный возможный тип.
- IQueryable — дырявая абстракция, потому что она требует, чтобы вы знали, какие выражения LINQ может понять ORM.
- IList и Array не подходят, потому что они изменяемы.
- Имейте в виду, что некоторые реализации IEnumerable также являются дырявыми абстракциями.

Источник: https://enterprisecraftsmanship.com/posts/which-collection-interface-to-use/
👍16
День 1492. #BestPractices
Лучшие Практики Журналирования
Независимо от того, какое ПО вы разрабатываете, вы так или иначе ведёте журнал. Это самый простой инструмент наблюдения, который у нас есть. Есть много ловушек, которые могут привести к бесполезным, объёмным и запутанным журналам. Вот несколько практик, которые позволят писать более качественные и согласованные по всей системе журналы.

Журналы предназначены для разработчиков, вы будете единственным, кто их читает, поэтому, собираясь что-то логировать, спросите себя:
- Поможет ли информация, которую вы собираетесь записать, отладить/понять поток?
- Это нужно писать в журнал? Нельзя ли получить эту информацию из других мест?
- Может ли логируемый объект быть огромным в производственной среде? Если да, можно ли записать только несколько его атрибутов?

Когда вы решите, что писать в журнал, нужно понять, как это делать.

1. Согласованность
Нужно поддерживать согласованность журналов во всей системе. Она ведёт к предсказуемости, что означает, что вы сможете осуществлять поиск по журналу, не вспоминая, в каком формате пишутся логи.

2. Уровень
Важно выбрать правильный уровень журналирования. В .NET определены следующие уровни:
- Trace (0) - наиболее подробные сообщения. Они могут содержать конфиденциальные данные приложения. Этот уровень отключен по умолчанию и никогда не должен включаться в производственной среде.
- Debug (1) - используются для разборов и отладки во время разработки. Они должны в первую очередь содержать информацию, полезную для отладки, и не иметь долгосрочной ценности.
- Information (2) - отслеживают общий поток исполнения приложения. Они должны иметь долгосрочную ценность.
- Warning (3) - выделяют ненормальное или неожиданное событие в потоке приложения, но не приводят к остановке выполнения приложения.
- Error (4) - выделяют, когда текущий поток выполнения останавливается из-за сбоя. Они должны указывать на сбой в текущем действии, а не на сбой всего приложения.
- Critical (5) – описывают неустранимый сбой приложения или системы или катастрофический сбой, требующий немедленного вмешательства.
- None (6) - не используется для записи сообщений журнала. Обозначает уровень на котором не должны записываться никакие сообщения.

Наиболее распространённые ошибки — это ведение слишком подробных журналов уровня Information или полное отсутствие использования уровня Debug.

3. Бережливость
Какой бы сервис логов вы ни использовали, он стоит денег, и быстрый способ сжечь деньги — писать в лог весь объект в виде json, который был относительно небольшим при разработке, но стал огромным в производственной среде. Журналы с огромными объектами бесполезны, их трудно читать, но они существуют, потому что так проще.
Выберите атрибуты, которые являются наиболее важными и полезными для лога и которые действительно помогут вам разобраться в потоке исполнения. Иногда нужно знать, пустой ли объект или его свойство, тогда запишите в лог это, а не сам объект.

4. Уникальность
Каждое сообщение журнала в системе должно быть уникальным. Если я открою журнал конкретного сервиса, я буду сбит с толку, увидев одни и те же логи из разных функций внутри сервиса. Мне будет проще начать отладку проблемы, чем копаться в тысячах одинаковых записей журнала. Т.е. журнал теперь бесполезен. Один из способов сохранить уникальность логов — обозначить имя сервиса и имя функции в качестве префикса для лога. Так гарантируется уникальность или, по крайней мере, сужается область поиска записей со всего сервиса до одной функции.

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

Источник: https://www.16elt.com/2023/01/06/logging-practices-I-follow/
👍12
День 1493. #ЗаметкиНаПолях #TipsAndTricks
Используем Сгенерированное Регулярное Выражение в Ограничениях Маршрута
ASP.NET Core
Регулярное выражение на основе сгенерированного кода предоставляет несколько преимуществ по сравнению с традиционным классом Regex:
- Более быстрое время запуска, так как весь код генерируется во время компиляции. Не нужно анализировать шаблон регулярного выражения и генерировать оптимизированный код для выполнения регулярного выражения во время выполнения.
- Улучшенная поддержка тримминга. Код, который не используется, не включается в окончательный бинарный файл.
- Улучшенная поддержка отладки. Вы можете пройтись по коду и посмотреть, что происходит.

Чтобы использовать генератор исходного кода для регулярных выражений, вам необходимы .NET 7 и C# 11. Для стандартных регулярных выражений IDE вроде Visual Studio или Rider даже предложат вам автоматически преобразовать их на использование генераторов кода.

Маршрутизация ASP.NET Core позволяет устанавливать ограничения на параметры маршрута. Ограничение может быть шаблоном регулярного выражения. В большинстве случаев этот шаблон – строковая константа:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseRouting();

app.MapGet(
"{value:regex(^([01]?[0-9]|2[0-3]):[0-5][0-9]$)}",
(string value) => "Время: " + value);

app.Run();

Этот код гарантирует, что параметр value соответствует времени в 24-часовом формате.

Однако ASP.NET Core предоставляет возможность создавать собственные ограничения путем реализации IRouteConstraint или наследования от класса, реализующего этот интерфейс. В этом случае вы можете использовать RegexRouteConstraint и предоставить регулярное выражение в конструкторе:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRouting(options =>
{
// регистрируем ограничение
options.ConstraintMap.Add(
"time", typeof(TimeRouteConstraint));
});

var app = builder.Build();
app.UseRouting();

app.MapGet(
"{value:time}",
(string value) => "Время: " + value);

app.Run();

sealed partial class TimeRouteConstraint
: RegexRouteConstraint
{
[GeneratedRegex("^([01]?[0-9]|2[0-3]):[0-5][0-9]$")]
private static partial Regex TimeRegex();

public TimeRouteConstraint()
: base(TimeRegex())
{
}
}

Источник: https://www.meziantou.net/using-source-generated-regex-in-asp-net-core-routing.htm
👍11
День 1494. #юмор
👍13
День 1495. #ЧтоНовенького
Выпущена Visual Studio 17.6 Preview 1
Первая превью версия Visual Studio 2022 17.6 уже доступна. Вот некоторые новинки, которые уже можно попробовать.

1. Раскрашивание пар скобок
Теперь вы можете визуально различать наборы открывающих и закрывающих фигурных скобок в вашем коде, что упрощает просмотр области действия вашего кода или поиск недостающих фигурных скобок.
Это доступно в C#, C++, TypeScript, JavaScript, Visual Basic и Razor.
Вы можете включить функцию в Tools > Options > Environment > Preview Features (Инструменты > Параметры > Среда > Предварительный просмотр функций) и отметив флажок Enable Brace Pair Colorization (Включить раскрашивание пар фигурных скобок). См. рисунок 1 ниже.

2. Построчный Git Unstage
В дополнение к построчному Git Stage добавлена функция unstage. См. рисунок 2 ниже.

3. GitHub Issues
Интеграция с GitHub Issues позволяет вам искать и ссылаться на ваши недавние проблемы из окна сообщения коммита в VS. Вы можете сослаться на проблему или пул-реквест, набрав # или нажав кнопку # в нижней правой части текстового поля сообщения коммита. В выпадающем списке будут последние открытые проблемы и пул-реквесты, которые были назначены текущему пользователю, прокомментированы им или в которых он упоминался. См. рисунок 3 ниже. Если вы не прошли аутентификацию в GitHub, вам будет предложено войти в систему, чтобы воспользоваться этой функцией.

Также при изучении истории коммитов в окне репозитория Git вы можете просматривать связанные проблемы и пул-реквесты в области сведений о коммите. Дважды щёлкните на коммит в окне репозитория Git, чтобы открыть сведения. Вы получите дополнительный контекст при просмотре истории Git, чтобы понять, почему произошли изменения. См. рисунок 4 ниже.

4. API примеров использования IntelliCode для C#
Вы когда-нибудь хотели легко найти примеры кода для API, с которыми вы работаете? API примеров использования IntelliCode делает это реальностью. Это функция, позволяющая увидеть реальные примеры того, как другие разработчики использовали данную функцию. Примеры взяты из общедоступных репозиториев с открытым исходным кодом на GitHub. Чтобы использовать эту функцию, наведите указатель мыши на любую поддерживаемую функцию и нажмите ссылку GitHub Examples and Documentation (Примеры и документация на GitHub). См. рисунок 5 ниже.

Источники:
-
https://devblogs.microsoft.com/visualstudio/try-visual-studio-2022-v17-6-preview-1/
-
https://devblogs.microsoft.com/visualstudio/reference-github-issues-and-pull-requests-in-visual-studio/
👍5
День 1496. #ProjectManagement
Хватит Говорить: «Технический Долг». Начало
Представления людей о «техническом долге» немного отличаются.

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

Звучит знакомо? Это разочаровывающий разговор.

Но мы часто сами приводим себя в эту ситуацию. Мы пытаемся объединить бизнесменов, дизайнеров, продакт-менеджеров и инженеров, используя фразу «технический долг». Но для каждого эта фраза означает своё. Спросите 10 технических специалистов, что такое технический долг, скорее всего получите 5-7 разных ответов.

Все связывают этот термин с чувством — обычно разочарованием, — но у них нет точного представления о том, откуда это чувство берется. Поэтому они навешивают этот термин на всё, что их беспокоит или пугает. Дизайнеры говорят, что дизайн выглядит не так, как они планировали. Менеджеры скажут, что это задержки в графике релизов функций. Ответы разработчиков будут вариациями на тему «плохого кода».

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

Мы, инженеры, должны скорректировать терминологию.

Приравнивание технического долга к просто плохому коду имеет несколько проблем.

1. Позволяет думать, что предыдущие разработчики просто халтурили, что нетактично, но нормально, пока мы не поймем, что на самом деле существовало ограничение, о котором мы не знали. Это ограничение объясняет отвратительные характеристики кода, а также мешает нам реализовать наше гениальное решение.

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

2. Приравнивание технического долга к просто плохому коду создаёт впечатление, что, если мы просто напишем хороший код, у нас не будет технического долга. Поэтому мы не тратим время на обзоры, тесты и комментарии. И через год мы там же, с чего начинали.

3. Позволяет нам путать «этот код не соответствует моим личным предпочтениям» с «этот код является проблемой» — что, опять же, нормально, пока мы не ограничены во времени. Мы тратим «неделю технического долга», занимаясь рефакторингами, вместо того чтобы что-то исправлять. Инженеры любят рефакторить код, чтобы он выглядел лучше. В итоге работать с кодом не становится легче, чем раньше: код просто другой, и никто, кроме автора, больше его не знает. Это одна из основных причин того, что недели «выплаты технического долга» часто мало или совсем ничего не прибавляют к скорости команды после них.

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

Источник:
https://stackoverflow.blog/2023/02/27/stop-saying-technical-debt/
👍12
День 1497. #ProjectManagement
Хватит Говорить: «Технический Долг». Окончание
Начало

Чтобы решить эти проблемы, выберите что-то измеримое для оценки качества системы, например: нагрузка по обслуживанию. Сколько времени и усилий разработчики тратят на задачи, не связанные с добавлением или удалением функций? Если у нас 6 разработчиков, но половина работы приходится на обслуживание, то надо планировать выпуск функций, полагаясь только на 3 разработчиков. Мы также можем отслеживать это число и определять, насколько быстро оно растёт с течением времени. Чем быстрее растёт нагрузка по обслуживанию, тем больше разочарований мы можем ожидать. Нулевой рост означает, что мы всегда можем обслуживать систему с той же долей нашей команды инженеров.

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

Снижение нагрузки по обслуживанию делает код более удобным для сопровождения с течением времени. Это требует выделения отдельных задач обслуживания, отслеживания их происхождения и устранения этих проблем в источнике. Эти рутинные работы, подкреплённые эмпирическими данными, дают нам что-то конкретное для обсуждения на встречах о техническом долге.

Выполняем ли мы сейчас много рутинных обновлений библиотек или фреймворков? Может быть, нужно регулярно выделять время на них. Чем больше их накапливается, тем сложнее становится отлаживать взаимодействие между выпусками разных библиотек. И чем реже программисты их выполняют, тем меньше у них практики, что делает обновление более сложным и болезненным, когда оно становится обязательным.

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

Мы работаем с устаревшей унаследованной архитектурой, основанной на предположениях о предметной области, которые больше не соответствуют действительности? Может быть, нужно создать план перестройки архитектуры.

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

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

Источник: https://stackoverflow.blog/2023/02/27/stop-saying-technical-debt/
👍6
День 1498. #ЧтоНовенького
Blazor United Включён в Дорожную Карту .NET 8
В конце января создатель Blazor Стив Сандерсон представил Blazor United https://youtu.be/48G_CEGXZZM. Проект направлен на объединение разрозненных компонентов экосистемы ASP.NET Core для формирования новой инструментальной среды, заимствуя технологии у Razor Pages, MVC, CSHTML, Blazor Server и Blazor WebAssembly.

«Мы начали несколько экспериментов, чтобы объединить преимущества Razor Pages, Blazor Server и Blazor WebAssembly в одном продукте, что позволит компонентам Blazor стать единой архитектурой для всех ваших сценариев веб-UI: для простого рендеринга HTML или для полной интерактивности либо на стороне сервера, либо в WebAssembly — и всё это в одном проекте с возможностью легко переключаться между различными режимами рендеринга и даже смешивать их на одной странице».

В видео он попросил оставлять отзывы о том, кажется ли Blazor United актуальным для разработчиков и каковы будут их сценарии и приоритеты. Он также сказал, что команда серьезно настроена сделать что-то вроде Blazor United для .NET 8, который выйдет в ноябре.

В целом полученная обратная связь была хорошей.

Вот что другой разработчик и автор Blazor Крис Сэйнти думает о проекте:
«Я в восторге от Blazor United по нескольким причинам, — сказал Сэйнти журналу Visual Studio Magazine. — Во-первых, похоже, что это позволит нам преодолеть некоторые из основных компромиссов при использовании Blazor WebAssembly или Blazor Server. С Blazor Wasm главной проблемой всегда было время, чтобы загрузить приложение и запустить его – ведь нужно скачать всю среду исполнения .NET. С Blazor Server проблема заключалась в необходимости постоянного подключения через SignalR и возникающих проблемах масштабирования. Эта возможность смешивать модели хостинга была тем, о чём люди просили с самых первых дней существования Blazor».

Автоматический режим, показанный Стивом, демонстрирует возможность использования Blazor Server для начальной загрузки страницы, обеспечивающей почти немедленный запуск, и загрузку ресурсов WebAssembly в фоновом режиме. При последующих посещениях используется модель WebAssembly, что устраняет необходимость в постоянном соединении.

«Вторая вещь, которая меня волнует, — это возможность просто отображать страницы на традиционном сервере с использованием модели программирования Blazor. Мне кажется, это сделает MVC и Razor Pages устаревшими, поскольку разработчики смогут использовать Blazor для любого типа веб-приложений в будущем. Я знаю, что не всем это понравится, но я бы хотел, чтобы Microsoft отказалась от MVC и Razor Pages и двигалась вперед с Blazor в качестве единственной модели для создания веб-интерфейса на основе стека Microsoft. Это значительно упростит вход в экосистему. Когда также будет добавлен Blazor Hybrid, разработчики могут потратить время на изучение одной модели программирования и создавать веб-UI и UI для настольных компьютеров и мобильных устройств в одной среде».

За ходом проекта можно следить в ветке Blazor United репозитория ASP.NET Core на GitHub.

Источник: https://visualstudiomagazine.com/articles/2023/02/03/blazor-united-plan.aspx?m=1
👍10
День 1499. #ЗаметкиНаПолях
Как Использовать yield break в C#?
Когда мы помещаем оператор yield внутрь метода, компилятор обрабатывает этот метод как метод (блок) итератора. Метод итератора может возвращать IEnumerable, IEnumerator, IEnumerable<T> или IEnumerator<T>. Он использует оператор yield return для возврата элементов один за другим. Компилятор обрабатывает метод итератора не так, как обычный метод. Про yield return я уже писал ранее, сегодня рассмотрим yield break.

Как только мы определим метод итератора с помощью yield return, нам понадобится способ остановить итерацию в какой-то момент. Например, когда выполняется какое-то условие, или после выполнения заданного количества итераций и т. д. Для этой цели мы можем использовать оператор yield break. Он завершит итерацию и выйдет из блока итератора. Рассмотрим метод, который будет генерировать случайные года между 1901 и 2023. Он будет выполняться бесконечно, поэтому прервём его, если будет найден високосный год:
public static class Utils
{
private static Random random = new();
public static IEnumerable<int> RandomYears()
{
int year;
while (true)
{
year = random.Next(1901, 2023);
yield return year;

if (year % 4 == 0)
{
Console.WriteLine($"Leap Year:{year}");
yield break;
}
}
Console.WriteLine($"Finished");
}
}

Теперь посмотрим на результаты:
foreach (int year in Utils.RandomYears())
Console.WriteLine(year);

Вывод:
1937
1986
1942
1920
Leap Year:1920

Может возникнуть вопрос: можем ли мы просто использовать break для завершения итерации вместо yield break? Да, но они ведут себя по-разному:
- yield break внутри блока итератора полностью завершает блок итератора и выходит из метода. Даже если после цикла есть какие-то операторы, компилятор их не выполнит. Как это видно в результатах выше, нет строки “Finished”. В этом случае компилятор даже выдаст предупреждение о том, что он обнаружил недостижимый код.
- break внутри цикла, он завершает цикл и выполняет следующий оператор после цикла. Т.е., если есть другие операторы после цикла, компилятор все равно выполнит их перед выходом из метода.

Если в коде выше заменить yield break на break и выполнить код, мы увидим выполнение оператора после цикла:
1969
1995
1920
Leap Year:1920
Finished

Мы можем думать об операторе yield break внутри метода итератора как о чём-то похожем на оператор return внутри обычного метода, который ничего не возвращает. Но можем ли мы просто использовать return для выхода из итератора вместо использования yield break? Нет. Мы увидим ошибку компиляции: CS1622 - Cannot return a value from an iterator. Use the yield return statement to return a value, or yield break to end the iteration. (Невозможно вернуть значение из итератора. Используйте оператор yield return, чтобы вернуть значение, или yield break, чтобы завершить итерацию.)

Итого
Методы итератора отличаются от обычных методов и не поддерживают обычные операторы возврата. Он может использовать только операторы yield return и yield break. Это очень важное различие между методами итератора и обычными методами. Метод итератора может иметь операторы «yield return» для возврата значений одно за другим и операторы yield break для выхода из итератора. Но мы не можем использовать обычный оператор return в методе итератора.

Источник: https://code-maze.com/csharp-yield-break/
👍16
День 1500. #ЗаметкиНаПолях
Избавляемся от Предупреждений Nullable-Типов в Объектах JSON
Использование обнуляемых ссылочных типов поможет вам избежать неприятных ошибок, а IDE сообщит вам о местах потенциальных проблем. Но как их использовать в типах, которые десериализуются из JSON?

Проблема в следующем: в проекте обычно есть несколько объектов передачи данных (DTO) или обычных объектов (POCO), которые объявляют свойства для десериализации данных. Вы точно знаете, что данные будут там после десериализации, поэтому объявляете свойства необнуляемыми. Тем не менее, компилятор (и IDE) настаивают на том, чтобы вы либо сделали свойство обнуляемым, либо инициализировали его. Есть несколько вариантов исправить это, каждый со своими преимуществами и нюансами.

1. Сделать свойство обнуляемым
public class User
{
[JsonProperty("name")]
public string? Name { get; set; }
}
Это уберёт предупреждение, но теперь придётся везде проверять свойство Name на null. Если вы точно знаете, что свойство всегда будет иметь значение, это добавляет много накладных расходов в вашу кодовую базу.

2. Добавить значение default! (пожалуйста, не надо)
Вы можете инициализировать свойство значением default!. Это фактически устанавливает значение в null, но подавляет предупреждение.
public string Name { get; set; } = default!;

Не делайте этого. Если десериализованный JSON не содержит значения свойства Name, теперь оно будет содержать null. Компилятор и IDE не будут предупреждать вас об этом, т.е. во время выполнения может быть выброшено неожиданное NullReferenceException.
Смысл обнуляемых ссылочных типов в том, чтобы предоставить вам систему безопасности, а это саботирует её. Подробнее про обнуляемый null можно почитать здесь.

3. Добавить первичный конструктор
Если вы используете Newtonsoft.Json, вы можете добавить в свой класс первичный конструктор, который устанавливает свойства. Десериализатор обнаружит это и вызовет конструктор вместо прямой установки свойств:
public class User
{
public User(string? name)
{
Name = name ?? "Unknown";
//или ArgumentNullException.ThrowIfNull(name)
}

[JsonProperty("name")]
public string Name { get; init; }
}
Предупреждение исчезнет, и ваш класс близок к JSON, который хотите десериализовать. Если вы уверены, что в JSON не будет null, имеет смысл использовать свойство, не допускающее null, и в C#.
Можно либо установить значение по умолчанию, либо выбрасывать ArgumentNullException в конструкторе. Последний вариант может означать исключение во время выполнения, но оно возникнет только тогда, когда что данные JSON не соответствуют вашим ожиданиям, и могут потребоваться другие действия (например, запись инцидента в лог) вместо того, чтобы просто продолжать выполнение кода.

4. Использовать required свойство
В C# 11 можно использовать модификатор required. В этом случае компилятор ожидает, что свойство всегда будет инициализировано и предупреждения не будет:
public required string Name { get; set; }

Такой подход чётко формулирует ожидания, не предоставляя компилятору и IDE ложной информации.
Имейте в виду, что модификатор required применяется во время компиляции, а не во время выполнения. Если JSON содержит null, нет никакой гарантии, что вы избежите NullReferenceException. В этом случае лучше использовать string? и выполнять проверки на null, где это применимо.

Источник: https://blog.maartenballiauw.be/post/2023/01/12/getting-rid-of-warnings-with-nullable-reference-types-and-json-object-models-in-csharp.html
👍8
День 1501. #Карьера
Почему Программирование Похоже на Спорт
Думайте о программировании как о спорте. Вам не нужно любить спорт или преуспевать в нём. Тем не менее, это хорошая аналогия.

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

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

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

Любой программист посоветует вам приложить много усилий для практики кодирования. Так же, как если вы хотите добиться успеха в теннисе, вы должны много тренироваться играть. То же самое с написанием кода. Практика, практика, практика! И хотя это отличный совет, можно продолжить аналогию и сосредоточимся на идее «набора навыков».

Переключимся на баскетбол. Школьные тренеры по баскетболу часто разбивают время тренировок на отдельные упражнения, сосредотачиваясь только на одном навыке, например:
- Броски
- Дриблинг
- Пасы
- Работа ног
- Навыки защиты

Чтобы добиться успеха в баскетболе, вы должны хорошо выполнять эти отдельные упражнения. Играя в настоящую игру, вы должны быстро переключаться между этими навыками. Время, потраченное на отработку каждого навыка, поможет делать это автоматически. Поэтому вместо того, чтобы думать: «Как мне сделать пас?» вы можете сосредоточиться на игре, происходящей вокруг вас, чтобы вопрос был «надо ли делать пас?». При достаточной практике вы можете вообще не думать; вы будете действовать по наитию!

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

Почему вы не практикуете этот недостающий навык? Скорее всего, вы либо не знаете, что нужно практиковать, либо не знаете, как это сделать. Хотя, есть ещё третий вариант: вы знаете, над чем вам нужно поработать, но избегаете этого, потому что это слишком сложно или болезненно.

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

Итого
Поскольку программирование по своей сути - навык, это объясняет такое понятие как ад учебных пособий (Tutorial Hell). Просмотр или чтение учебных пособий может быть полезным, но ничто не заменит отработку ваших навыков в проекте. Просмотр видео на Youtube или Udemy не заведёт вас далеко, потому что это больше похоже на просмотр тенниса, чем на игру в него. Вы наверняка получите какие-то концептуальные знания, но не разовьёте навыки.

PS: вот примерный список навыков для .NET разработчика на 2023й года от Ника Чапсаса https://raw.githubusercontent.com/Elfocrash/.NET-Backend-Developer-Roadmap/master/roadmap-dark-compact-2023.png

Источник: https://betterprogramming.pub/why-coding-is-like-playing-sports-848fbb21282
👍23👎1
День 1502. #DesignPatterns
Паттерн Скромный Объект
Скромный Объект (Humble Object) — это паттерн проектирования, специально предназначенный для упрощения модульного тестирования путём отделения поведения, которое легко обработать (логика предметной области), от поведения, которое трудно обработать (например, внешние события или зависимости). Рассмотрим, что это такое и как его можно использовать.

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

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

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

У вас когда-нибудь была зависимость от DateTime.Now? Если да, может быть очень сложно иметь стабильные тесты в зависимости от того, что вы делаете с Now. Тесты могут работать в будние дни, но проваливаться в выходные, если вы проверяете это в коде. Поэтому мы можем просто «извлечь» эту часть и поместить её в оболочку:
public interface IDateTimeProvider
{
DateTime Now { get; }
}

public class DateTimeProvider : IDateTimeProvider
{
public DateTime Now => DateTime.Now;
}

public class MyService
{
public MyService(
IDateTimeProvider dateTimeProvider)
{

}
}

Мы отделили трудно тестируемые вещи от легко тестируемых. Трудно тестируемые вещи, в идеале, вообще не нуждаются в тестировании. Тестировать DateTime.Now вообще не лучшая идея. Во-первых, как бы вы реализовали такой тест? А во-вторых, это не ваш код. С вашей точки зрения, DateTime.Now — это сторонний код, и не ваша задача его тестировать, просто допустим, что он работает правильно.
В то же время легко тестируемые вещи, как MyService, теперь получают зависимость, которая находится под вашим контролем. В тестах её можно заменить пустышкой или моком.

Итого
Паттерн Humble object может облегчить вашу жизнь, особенно на границах вашей архитектуры. Мы рассмотрели его на примере системного времени, но вы также можете применять это правило для таких вещей, как доступ к вводу-выводу или сети.

Источник: https://steven-giesel.com/blogPost/47acad0a-255c-489b-a805-d0f46bde23e5
👍16👎1
День 1503. #ЗаметкиНаПолях
Использование Нескольких Контекстов EF Core. Начало
EF Core использует DbContext, который представляет сеанс с БД и отвечает за отслеживание изменений, выполнение операций и управление подключениями. Обычно в приложении используется только один DbContext. Но что, если нужно иметь несколько?

Зачем?
1. Несколько баз данных. Вы вынуждены использовать несколько контекстов, каждый из которых предназначен для своей БД.
2. Разделение проблем. Если модель предметной области сложная, можно упростить себе жизнь, разделив задачи между несколькими контекстами, каждый из которых отвечает за определённую часть.
3. Модульный монолит. Вы можете настроить свою схему базы данных для каждого контекста, обеспечивая логическое разделение на уровне базы данных.
4. Экземпляр для чтения. Вы можете настроить отдельный экземпляр DbContext только для чтения из БД, например, установив QueryTrackingBehavior.NoTracking на уровне DbContext, чтобы повысить производительность запросов.

Рассмотрим пример с двумя контекстами:
- оба используют одну и ту же БД,
- каждый обращается к отдельной схеме.
public class CatalogDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
public class OrderDbContext : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<LineItem> LineItems { get; set; }
}

Сначала нам нужно добавить контексты в контейнер DI:
services.AddDbContext<CatalogDbContext>(opt =>
opt.UseSqlServer("CONNECTION_STRING"));
services.AddDbContext<OrderDbContext>(opt =>
opt.UseSqlServer("CONNECTION_STRING"));

Если вы просто хотите использовать оба контекста в одной схеме, то это всё. Если вы хотите настроить разные схемы для каждого, необходимо переопределить метод OnModelCreating и указать схему с помощью HasDefaultSchema:
public class CatalogDbContext : DbContext
{

protected override void
OnModelCreating(ModelBuilder mb)
{
mb.HasDefaultSchema("catalog");
}
}

public class OrderDbContext : DbContext
{

protected override void
OnModelCreating(ModelBuilder mb)
{
mb.HasDefaultSchema("order");
}
}

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

Источник:
https://www.milanjovanovic.tech/blog/using-multiple-ef-core-dbcontext-in-single-application
👍5
День 1504. #ЗаметкиНаПолях
Использование Нескольких Контекстов EF Core. Окончание
Начало

Ограничения
- Невозможно выполнять соединения между разными экземплярами DbContext, поскольку EF Core не знает, используют ли они одну и ту же базу данных.
- Транзакции будут работать, только если DbContexts используют одну и ту же базу данных. Вы должны создать новую транзакцию и поделиться ею между контекстами, вызвав метод UseTransaction().

Таблица истории миграций
Если вы решите использовать разные схемы для каждого DbContext, вы можете быть неприятно удивлены, узнав, что схема по умолчанию не применяется к таблице истории миграций. Нужно настроить это, вызвав метод MigrationsHistoryTable и указав имя таблицы и схему, в которой будет храниться история миграций для этого контекста:
services.AddDbContext<CatalogDbContext>(opt =>
opt.UseSqlServer(
"CONNECTION_STRING",
o => o.MigrationsHistoryTable(
tableName: "efmigrations",
schema: "catalog")));

services.AddDbContext<OrderDbContext>(opt =>
opt.UseSqlServer(
"CONNECTION_STRING",
o => o.MigrationsHistoryTable(
tableName: "efmigrations",
schema: "order")));

Преимущества использования нескольких контекстов
Использование нескольких DbContexts может дать вашему приложению несколько преимуществ:
- Разделение ответственности
Каждый DbContext может отвечать за определённое подмножество данных приложения, что может помочь организовать код и сделать его более модульным.

- Повышение производительности
Когда вы разделяете доступ к данным на несколько DbContext, приложение может снизить риск конфликтов и улучшить параллелизм, что может повысить производительность.

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

Источник: https://www.milanjovanovic.tech/blog/using-multiple-ef-core-dbcontext-in-single-application
👍9