.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
День 1638. #PostgresTips
Советы по Postgres для Начинающих
Давно я не писал ничего про базы данных. А тут наткнулся на советы по PostgreSQL и решил пройтись по ним, а заодно повторить для себя некоторые моменты, поскольку скоро мне предстоит переход на эту СУБД. Это будет серия постов с тегом #PostgresTips, которые периодически будут появляться на канале.

1. Кортежи — это физические версии строк
Одним из основополагающих аспектов PostgreSQL, который удивляет многих новичков, является концепция кортежей. Попросту говоря, кортеж в Postgres — это физическая версия строки данных. Это означает, что при изменении данных в строке вместо изменения существующих данных Postgres добавляет новую версию этой строки, кортеж. Эта система управления версиями называется MVCC (Multiversion Concurrency Control), и важно понимать её для разработки высокопроизводительных систем.

Вот что происходит во время различных операций записи:
- Когда вы выполняете команду DELETE, она не сразу освобождает место на диске. Вместо этого старый кортеж помечается как мёртвый, но остаётся в БД до тех пор, пока VACUUM не удалит его. Если эти мёртвые кортежи накапливаются и удаляются при очистке больших объёмов, это приводит к раздуванию таблиц и индексов (они занимают гораздо больше места на диске, чем содержат данных).
- Точно так же, когда вы обновляете строку, Postgres не изменяет существующий кортеж. Вместо этого он создаёт новую версию этой строки (новый кортеж) и помечает старую как мёртвую.
- Даже отменённый INSERT создаёт мёртвый кортеж, что может удивить многих. Это означает, что, если вы попытаетесь вставить запись, а затем откатите это действие, кортеж, который должен был быть вставлен, помечается как мёртвый.

Чтобы помочь понять эти концепции, каждая таблица в Postgres имеет скрытые столбцы, которые вы можете посмотреть: ctid, xmin и xmax. ctid представляет расположение кортежа (номер страницы + смещение внутри неё), а xmin и xmax можно рассматривать как «дату (номер транзакции) рождения» и «дату смерти» для кортежей.

Поняв это поведение на раннем этапе, вы будете лучше подготовлены к решению проблем, связанных с дисковым пространством, раздуванием и процессами автоочистки, которые направлены на удаление этих мёртвых кортежей. Вот базовый пример, тривиальный, но очень важный:
pg=# create table t1 as select 1 as id;
SELECT 1
pg=# select ctid, xmin, xmax, * from t1;
ctid | xmin | xmax | id
-------+-------+------+----
(0,1) | 47496 | 0 | 1
(1 row)

pg=# update t1 set id = id where id = 1;
UPDATE 1
pg=# select ctid, xmin, xmax, * from t1;
ctid | xmin | xmax | id
-------+-------+------+----
(0,2) | 47497 | 0 | 1
(1 row)

Мы создали таблицу с одной строкой, проверили расположение живого кортежа этой строки (ctid), а затем сделали обновление, которое логически ничего не делает, т.е. не меняет значение. Но местоположение изменилось с (0,1) (страница 0, смещение 1) на (0,2). Потому что физически Postgres создал новый кортеж — новую версию строки. Понимание этого поведения Postgres поможет вам проектировать системы, работающие более эффективно.

Источник: https://postgres.ai/blog/20230722-10-postgres-tips-for-beginners
👍26👎1
День 1639. #ЗаметкиНаПолях
Использовать Записи, Классы или Структуры
Классы и структуры были частью C#, начиная с версии 1.0, но совсем недавно были добавлены записи. Сегодня сравним, где что лучше использовать.

Классы
Классы - наиболее универсальная из доступных структур данных, но требует больше ресурсов. Это не значит, что классы — плохой выбор. Высоко оптимизированные классы предлагают широкий спектр функций, что делает их мощным инструментом для написания кода. Вот типичное определение класса:
public class ApiClient : HttpClient
{
private string _myField = "";
public string MyProperty { get; set; }
public ApiClient()
{
BaseAddress = new Uri("https://api.com");
}
public string MyMethod()
=> "hello";
}

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

Структуры
Структуры лучше всего использовать для представления простых данных с небольшим поведением или без него. Вот типичная реализация структуры:
public struct GeoLocation
{
private double _latitude;
private double _longitude;
public GeoLocation(double lat, double lng)
{
_latitude = lat;
_longitude = lng;
}
public override string ToString()
=> $"(lat: {_latitude}, lon:{_longitude})";
}

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

См. подробнее, когда использовать структуру

Записи
Записи — это облегчённые ссылочные типы с семантикой значений. Кроме того, компилятор «из коробки» предлагает некоторые функции, такие как равенство по значению и форматирование при выводе. Вот два способа определения записи:
// обычный
public record ApiData
{
public int Id { get; init; }
}
// с позиционными параметрами
public record ApiData(int Id);

Особенности
- Записи по умолчанию - ссылочные типы.
- Можно определить конструкторы и свойства.
- Можно использовать наследование.
- Используются как небольшие неизменяемые структуры данных.
- в C# 10 появились структуры-записи (record struct), которые являются структурами, поддерживают все свойства записей, кроме наследования.

Запись стоит использовать, когда нужно инкапсулировать данные без сложного поведения (распространённый пример этого сценария — объекты передачи данных — DTO). Если структура данных будет выполнять сложное поведение и будет большим экземпляром, это признаки того, что нужно использовать класс. Кроме того, лучше выбрать класс, если понадобится изменение данных экземпляра после создания.

Источник: https://code-maze.com/csharp-should-we-use-records-classes-or-structs/
👍15
День 1640. #ЗаметкиНаПолях
Разработка API для Людей.
Часть 4. Шаблоны разработки. Начало

Часть 1
Часть 2
Часть 3

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

1. Язык
Называть вещи трудно. Проблема в том, что, как и в случае с именами переменных и функций, вы хотите, чтобы маршруты API, поля и типы были понятными, но лаконичными.

Используйте простой язык
Это очевидно, но на практике довольно сложно сделать и может привести к «эффекту велосипедного сарая». Постарайтесь выразить самую суть понятия и не бойтесь использовать словарь синонимов. Например, не путайте понятия пользователя и клиента. Пользователь напрямую использует ваш API, клиент (или конечный пользователь) – тот, кто покупает товары или услуги, которые может предлагать ваш пользователь через ваш API.

Избегайте жаргона
Не думайте, что ваш пользователь знает всё о вашей конкретной отрасли. Например, 16-значный номер кредитной карты называется основным номером счета (Primary Account Number, PAN). В финтех-кругах, люди говорят о PAN, DPAN и FPAN, поэтому поймут, если в платёжном API будет:
card.pan = 4242424242424242;
Но для более широкой аудитории всё же лучше подойдёт:
card.number = 4242424242424242;
Это особенно важно, когда вы думаете о том, кто является аудиторией вашего API. Скорее всего, это разработчик, не знакомый с финансовыми терминами, поэтому лучше предположить, что люди не знакомы с жаргоном вашей отрасли.

2. Структура
Используйте enum вместо bool
Представим, что у нас есть API для модели подписки. Мы хотим, чтобы пользователи могли определить, активна подписка или отменена. Кажется разумным определить:
Subscription.canceled={true, false}
Это сработает, но что если вам нужно будет добавить приостановку подписки? Т.е. мы делаем перерыв в приеме платежей, но подписка активна и не отменена. Придётся добавить новое поле:
Subscription.canceled={true, false}
Subscription.paused={true, false}
Теперь, чтобы увидеть фактический статус подписки, нам нужно смотреть на два поля. А что, если они оба true? Можно ли приостановить подписку, которая была отменена?
Вместо этого проще сделать поле статуса с перечислением:
Subscription.status={"active", "canceled"}
Тогда приостановку легко добавить, добавив значение перечисления:
Subscription.status={"active", "canceled", "paused"}
Мы добавили функциональность, но сохранили сложность API на том же уровне, а также сделали его более описательным. Если мы когда-нибудь решим удалить функцию приостановки подписки, удалить значение перечисления всегда будет проще, чем удалить поле. Наверняка есть случаи, когда поле bool подойдёт лучше, но всегда рассматривайте возможность возникновения третьего варианта.

Используйте вложенные объекты для расширяемости
Пробуйте логически сгруппировать поля вместе. Это:
customer.address = {
line1: "Main Street 123",
city: "San Francisco",
postal_code: "12345"
};
выглядит гораздо понятнее, чем:
customer.address_line1 = "Main street 123";
customer.address_city = "San Francisco";
customer.address_postal_code: "12345";

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

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

Источник:
https://dev.to/stripe/common-design-patterns-at-stripe-1hb4
👍12
День 1641. #ЗаметкиНаПолях
Разработка API для Людей.
Часть 4. Шаблоны разработки. Окончание

Часть 1
Часть 2
Часть 3

Начало

3. Ответы
Возвращайте тип объекта
В большинстве случаев вызов API делается для получения или изменения данных. В последнем случае нормой является возврат изменённого ресурса. Например, если вы обновите email клиента, то в ответе вы ожидаете получить данные клиента с обновленным email.
Чтобы облегчить жизнь разработчикам, чётко укажите, что именно возвращается. Например, маршрут
/v1/customers/:customer/payment_methods/:payment_method
должен вернуть тип PaymentMethod для данного клиента. Это должно быть очевидно из маршрута, но на всякий случай, верните тип объекта в поле "object", чтобы избежать путаницы:
{
"id": "pm_123",
"object": "payment_method",
"created": 1672217299,
"customer": "cus_123",

}
Это очень помогает при поиске по логам или для добавления методов защитного программирования на клиенте:
if (response.Data.Object != "payment_method") 
{
// не тот объект, который ожидался
return;
}

4. Безопасность
Используйте систему разрешений
Допустим, для крупного клиента вы добавили новую функцию, чтобы они протестировали её в бета-версии. Новый маршрут не задокументирован, о нём никто не знает, поэтому можно не волноваться. Несколько недель спустя вы вносите изменения в функцию, о которых попросил крупный клиент… И получаете серию гневных писем от других пользователей, у которых всё сломалось. Оказывается, о вашем секретном маршруте узнали другие.
Теперь надо не только решать проблемы клиентов. Теперь «бета»-функция фактически выпущена, т.к. об изменениях в ней придётся сообщать всем.
Если вы хотите, чтобы закрытые API оставались закрытыми, убедитесь, что к ним нельзя получить доступ, без соответствующих разрешений. Самый простой способ — привязать систему разрешений к ключу API. Если ключ API не авторизован для использования маршрута, верните сообщение об ошибке со статусом 403.

Сделайте идентификаторы неугадываемыми
Если вы разрабатываете API, который возвращает объекты со связанными с ними идентификаторами, убедитесь, что эти их нельзя угадать или каким-либо иным образом реконструировать. Если идентификаторы просто последовательные, то в лучшем случае вы непреднамеренно выдаёте ненужную информацию о бизнесе, в худшем случае - сильно подрываете безопасность.
Например, после покупки на сайте я получил идентификатор подтверждения заказа «10». Я могу сделать два предположения:
- У вас не такой большой бизнес, как вы, вероятно, заявляете.
- Я потенциально могу получить информацию о 9 предыдущих заказах (и обо всех будущих), так как знаю их идентификаторы. Если указанный ниже маршрут не защищён системой разрешений, можно угадать идентификатор и возможно получить закрытую информацию о других ваших клиентах:
https://api.example.com/v1/orders/9
Делайте идентификаторы неугадываемыми, например, используя UUID. Он, по сути, представляет собой строку случайных чисел и букв, что означает, что невозможно угадать, как будет выглядеть следующий идентификатор, основываясь на том, который у вас есть. Вы теряете в удобстве (гораздо проще говорить о «заказе 42», чем о «заказе 123e4567-e89b-12d3-a456-426614174000»), но вы компенсируете это преимуществами безопасности. Не забудьте сделать его понятным для человека, добавив префиксы объектов.

Источник: https://dev.to/stripe/common-design-patterns-at-stripe-1hb4
👍10
День 1642.
Давайте сегодня обсудим. К этому меня подтолкнули рекомендации ютуба с «музыкой для программирования». Меня интересно, почему эта музыка всегда однотипная? С чего взялось убеждение, что лучше всего программируется под электро-техно-транс… вотэтовсё? Вкусы у всех разные, но почему-то «для программирования» всегда предлагается один жанр.

Что вы слушаете, когда пишете код?

Голосуйте ниже и пишите в комментариях.

Источник изображения: https://8tracks.com/explore/programming
👍6
День 1643. #PostgresTips
Советы по Postgres для Начинающих

2. Делайте EXPLAIN ANALYZE всегда с BUFFERS
Понимание того, как работает запрос, имеет решающее значение для оптимизации его производительности. В PostgreSQL команда EXPLAIN является основным инструментом для достижения этой цели. Однако для более детального разбора следует использовать EXPLAIN (ANALYZE, BUFFERS). И вот почему:
- EXPLAIN сам по себе предоставляет план запроса, давая вам представление об операциях, которые Postgres намеревается использовать для выборки или изменения данных: последовательное сканирование, сканирование индекса, объединение, сортировку и многое другое. Эту команду следует использовать исключительно для проверки плана запроса без выполнения.
- EXPLAIN ANALYZE не только показывает запланированные операции, но также выполняет запрос и предоставляет фактическую статистику времени выполнения. Это позволяет сравнивать, например, предполагаемые количества строк на каждом этапе с фактическими количествами, помогая определить, где Postgres может делать неточные предположения. Также ANALYZE предоставляет информацию о времени выполнения каждого этапа.
- EXPLAIN (ANALYZE, BUFFERS) предоставляет информацию об использовании буфера — в частности, сколько блоков попало в пул буферов или считано в него из базового кеша или диска. Это даёт ценную информацию о том, насколько интенсивен по дисковому чтению/записи ваш запрос, что может серьёзно сказаться на времени его выполнения. А если у вас задан параметр track_io_timing = on, вы получите информацию о времени выполнения для всех операций ввода-вывода.

3. Оптимальный выбор UI-инструментов: помимо pgAdmin
При изучении Postgres одним из первых вопросов, с которым вы столкнётесь, будет выбор клиента или IDE. Многие новички начинают с pgAdmin из-за его популярности и доступности. Но доступны более мощные и универсальные инструменты.

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

Если вы больше склоняетесь к графическим IDE, есть несколько, которые предлагают баланс между удобством для пользователя и расширенными возможностями: DBeaver, DataGrip от JetBrains и Postico предоставляют сложные UI-интерфейсы с поддержкой выполнения запросов, визуализации данных и многого другого.

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

Источник: https://postgres.ai/blog/20230722-10-postgres-tips-for-beginners
👍14
День 1644. #ЧтоНовенького
JetBrains Представили Предиктивную Отладку
Меня иногда спрашивают, почему я рассказываю о новинках или инструментах Visual Studio, но редко пишу про Rider или ReSharper. Ответ прост: «Я пользуюсь Visual Studio и не могу достоверно оценить инструменты от JetBrains, поэтому не хочу писать о том, чего сам не пробовал». Однако об этой новинке очень захотелось рассказать, потому что, на мой взгляд, это очень круто.

С введением инструментов отладки разработчики ПО получили возможность интерактивно исследовать поток исполнения программ для поиска ошибок в реальных средах. Эти инструменты постоянно совершенствовались по мере того, как росла сложность создаваемого ПО. Тем не менее, разработчики часто оказываются в ситуации пошаговой отладки с множеством перезапусков и разбросанными по коду точками останова. Теперь в ReSharper 2023.2 появился предиктивный отладчик (в Rider появится позднее). Его можно включить в меню Tools > Debugger > Editor Integration > Predictive Debugger (Инструменты > Отладчик > Интеграция с Редактором > Предиктивный Отладчик), отметив флажок Show predicted values (beta) (Показывать прогнозируемые значения (бета)).

Простой пример предиктивной отладки (см. анимацию ниже)
Цвета дают представление о том, что происходит:
- Выражения, выделенные зелёным или красным цветом, означают, что выражение было оценено как истинное или ложное соответственно.
- Операторы, выделенные серым цветом, указывают на то, что этот путь кода не будет выполняться (подобно мертвому коду).
- Значения в конце строк, выделенные синим цветом, показывают прогнозируемые значения после выполнения соответствующего оператора.
- Жёлтые или красные подсказки показывают, где заканчивается предсказание; например, когда метод возвращает значение, выдаётся исключение (перехваченное или не перехваченное) или программа останавливается (Environment.Exit).

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

Также предполагается использование аннотаций (например, из JetBrains.Annotations) для тонкой настройки.

Будущие улучшения и ограничения
Планируется использовать внешние аннотации, чтобы пометить код, который будет разрешено оценивать, чтобы уменьшить количество возможных прерываний из-за нечистых вычислений. Например, объявить File.Exists или int.TryParse «чистыми» функциями.

К сожалению, код async/await не поддерживается, поскольку отладчик не допускает многопоточных вычислений.

Источник: https://blog.jetbrains.com/dotnet/2023/07/27/introducing-predictive-debugging-a-game-changing-look-into-the-future/
👍10
День 1645. #ЗаметкиНаПолях
Scrutor в .NET
Сегодня рассмотрим библиотеку Scrutor, варианты её использования и что она предлагает для упрощения внедрения зависимостей, а также о реализации сквозных задач с использованием шаблона декоратора.

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

Использование Scrutor для сканирования сборки
Scrutor помогает упростить наш код внедрения зависимостей за счёт динамического поиска типов внутри сборок и их регистрации во время выполнения. Используя сканирование сборки, мы можем частично автоматизировать наш код регистрации зависимостей. Кроме того, мы можем использовать сканирование сборки для создания расширяемых систем. С помощью сканирования мы можем создать программу, способную находить и загружать дополнительные модули во время выполнения.
builder.Services.Scan(s => s
.FromCallingAssembly()
.AddClasses(c =>
с.InNamespaces("MyApp.Services"))
.AsImplementedInterfaces()
);

Здесь мы вызываем метод расширения Scan() который выполнит сканирование сборки (в данном случае - той, из которой он вызван) и выберет сервисы для регистрации на основе предоставленного нами селектора (в данном случае из пространства имён "MyApp.Services"). Наконец, мы регистрируем выбранные нами типы в качестве подстановки для всех интерфейсов, которые они реализуют, вызывая AsImplementedInterfaces(), т.к. обычно наши зависимости представляют собой интерфейсы (типа IUserService), а не классы напрямую.

Используя широкий спектр методов расширения, которые предоставляет Scrutor, мы можем очень точно определить сервисы, которые мы хотим зарегистрировать, например, из другой сборки (библиотеки классов):
builder.Services.Scan(s => s
.FromAssemblyOf<ICustomerService>()
.AddClasses(c =>
c.AssignableTo<ICustomerService>())
.AsMatchingInterface());

Здесь мы просим Scrutor сканировать сборку, в которой находится интерфейс ICustomerService, используя метод расширения FromAssemblyOf<ICustomerService>(). Затем среди всех реализаций в этой сборке мы выбираем только те, которые могут быть назначены ICustomerService.

Работа с обобщёнными типами
Если мы хотим добавить реализацию обобщённого типа, то вместо обобщённой реализации метода AssignableTo, мы можем использовать метод с аргументом типа:

.AddClasses(c =>
c.AssignableTo(typeof(IService<>)))


Указание времени жизни зависимостей
Мы можем использовать спецификаторы времени жизни зависимостей:
builder.Services.Scan(s => s

.WithTransientLifetime()
);
Или соответственно WithScopedLifetime() и WithSingletonLifetime().

Обработка нескольких реализаций
Стратегия регистрации — это то, как Scrutor обрабатывает случаи, когда для одного и того же интерфейса существует несколько реализаций:
builder.Services.Scan(s => s

.UsingRegistrationStrategy(RegistrationStrategy.Skip)
.AsImplementedInterfaces()
);
В этом коде, используя метод UsingRegistrationStrategy() с параметром RegistrationStrategy.Skip, мы указываем Scrutor игнорировать дополнительные реализации любого интерфейса после регистрации первой. Другие варианты:
- RegistrationStrategy.Append - добавляет новую регистрацию для существующих сервисов.
- RegistrationStrategy.Throw - выдаёт ошибку при попытке зарегистрировать существующий сервис.

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

Источник:
https://code-maze.com/dotnet-dependency-injection-with-scrutor/
👍17
День 1646. #ЗаметкиНаПолях
Scrutor в .NET. Окончание
Начало

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

Реализация простого декоратора
Мы можем использовать метод расширения Decorate() для управления регистрацией объектов-декораторов. Определим декоратор для репозитория, который делает запись в консоль, когда мы обращаемся к списку сущностей.

Для этого нам нужно наследоваться от того же интерфейса, который реализует репозиторий:
public class RepositoryLoggerDecorator<T> : IRepository<T>
{
private readonly IRepository<T> _decoratedRepo;
public RepositoryLoggerDecorator(
IRepository<T> decoratedRepo)
{
_decoratedRepo = decoratedRepo;
}
public IEnumerable<T> GetAll()
{
Console.WriteLine("Retrieved list of users");
return _decoratedRepo.GetAll();
}
}

Декоратор должен принимать в конструкторе экземпляр декорируемого репозитория, в нашем случае это экземпляр IRepository<T>, который мы будем оборачивать. В методе GetAll() мы записываем сообщение в консоль, а затем вызываем метод GetAll() обёрнутого объекта.

Регистрация декораторов в Scrutor
Для регистрации декоратора в контейнере зависимостей в Scrutor используется метод расширения Decorate<,>():
builder.Services
.Decorate<IRepository<User>,
RepositoryLoggerDecorator<User>>();

Первый аргумент типа метода Decorate<,>() представляет тип сервиса, который мы хотим декорировать, а второй аргумент типа — это тип декоратора.

После того, как мы зарегистрируем декоратор в Scrutor, все вызовы методов IRepository<User> будут перехвачены RepositoryLoggingDecorator<User>.

Замечание: В этом примере мы использовали метод Console.WriteLine() для ведения журнала, в реальном же сценарии мы бы использовали ILogger<T> и фреймворк журналирования.

Источник:
https://code-maze.com/dotnet-dependency-injection-with-scrutor/
👍7
День 1647. #ВопросыНаСобеседовании
Самые часто задаваемые вопросы на собеседовании по C#
Давненько не было ничего из этой рубрики. Нумерацию продолжу, если что, предыдущие вопросы доступны по тегу #ВопросыНаСобеседовании.

19. В чем разница между интерфейсами IEnumerable, ICollection и IList в C#?
IEnumerable, ICollection и IList — это интерфейсы, предоставляемые библиотеками платформы .NET для работы с коллекциями данных. Они имеют следующие особенности.

IEnumerable:
- Предоставляет базовые функции для итерации по коллекции с использованием цикла foreach.
- Поддерживает доступ только для чтения.
- Идеально подходит для ситуаций, когда вам нужно только перебирать коллекцию.
- Определяет только один метод: GetEnumerator(), который возвращает IEnumerator.

ICollection:
- Наследуется от IEnumerable.
- Представляет собой группу элементов с дополнительными функциями, такими как размер (свойство Count), перечисление и синхронизация (через свойства IsSynchronized и SyncRoot).
- Добавляет основные операции с коллекцией, такие как Add, Remove, Clear, проверку вхождения элемента (Contains) или проверку списка на доступность записи IsReadOnly.
- Поддерживает доступ как для чтения, так и для записи.

IList:
- Наследуется от ICollection.
- Представляет список элементов, доступных по индексу.
- Предоставляет расширенные возможности управления коллекцией, такие как вставка и удаление по индексу (методы Insert и RemoveAt).
- Поддерживает произвольный доступ к элементам через индексатор.

См. также
- «В чём разница между IEnumerable и IQueryable?»
- «Какой Интерфейс Коллекции Использовать?»

Источник: https://dev.to/bytehide/20-c-interview-questions-for-experienced-2023-1hl6
👍25
День 1648.
Сегодня порекомендую вам один из лучших, на мой взгляд, докладов DotNext Autumn 2022 (почти год ждал, когда его выложат в открытый доступ). Роман Неволин «Пишем приложения, которые не ломаются в продакшене»

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

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

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

3 О деплое
- Автоматизируйте все проверки, какие можете
- Наблюдайте за всем, что происходит в приложении
- Пишите постмортемы (что случилось, почему, как исправить в будущем)
- Действительно делайте то, что написано в постмортеме

Роман разбирает, как на каждом из этапов разработки уменьшить вероятность возникновения багов, как отслеживать их в тестировании и что делать, если вы все-таки задеплоили некорректный код.
👍27
День 1649. #юмор
👍27
День 1650. #ЗаметкиНаПолях
Используем Дискриминатор в Entity Framework
Допустим вы создаёте систему управления школой, в которой есть разные типы сотрудников Учитель и Администратор. Они имеют как общие свойства (Id, Имя, Дата найма), так и уникальные (например, Предмет у учителя и Отдел у администратора).
public class Staff
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime HireDate { get; set; }
}

public class Teacher : Staff
{
public string Subject { get; set; }
}
public class Administrator : Staff
{
public string Department { get; set; }
}

Если вы хотите получить всех сотрудников, вам нужно сделать отдельные свойства контекста и запросы для учителей и администраторов:
var teachers = context.Teachers.ToList();
var admins = context.Administrators.ToList();

Это требует не только больше кода, но и ручного управления этими отношениями. Для общих операций придётся дважды написать один и тот же код для учителей и администраторов. Также может быть сложнее поддерживать согласованность данных, особенно если в будущем появится больше типов сотрудников.

Столбец-дискриминатор в используется в сценариях наследования Table-per-Hierarchy, для представления отношений наследования в реляционной БД. Он предполагает использование одной таблицы для представления всех различных типов в иерархии со специальным столбцом (дискриминатор) для различения типов.
public class SchoolContext : DbContext
{
public DbSet<Staff> StaffMembers { get; set; }

protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<Staff>()
.HasDiscriminator<string>("StaffType")
.HasValue<Teacher>("Teacher")
.HasValue<Administrator>("Administrator");
}
}
В методе OnModelCreating мы вызываем HasDiscriminator для настройки столбца-дискриминатора, задавая ему имя (StaffType) и тип. Метод HasValue позволяет указать, какие значения следует использовать для представления каждого типа в иерархии.

Теперь в запросах StaffMembers EF Core будет использовать столбец-дискриминатор для правильного создания экземпляров Teacher или Administrator в зависимости от ситуации.
var staff = context.StaffMembers.ToList();
foreach (var s in staff)
{
switch (s)
{
case Teacher t:

case Administrator a:

}
}
Также можно добавлять экземпляры этих классов, и EF Core автоматически задаст нужное значение дискриминатора при вставке строки в БД.

Преимущества
1. Простота. Одна таблица для всей иерархии может упростить понимание и поддержку схемы БД.
2. Повторное использование кода. Общие операции для всех типов используют один и тот же код.
3. Производительность. Часто можно получить данные с меньшим числом обращений к БД.

Недостатки
1. Неиспользуемое пространство. В одной таблице должны быть столбцы для всех свойств всех типов в иерархии, даже если некоторые типы не используют определенные свойства.
2. Ограниченная гибкость. Подход может стать сложным, если у вас сложная иерархия наследования или если нужно смоделировать отношения «многие-ко-многим» между различными типами.
3. Целостность данных. С одной таблицей сложнее обеспечить целостность данных на уровне БД. Например, вы не можете запретить строке, которая должна представлять учителя, иметь значение в столбце «Отдел» (который должен быть специфичным для администраторов).

Источник: https://stefandjokic.tech/blog/
👍9
День 1651. #ЗаметкиНаПолях
Флаги функций в
ASP.NET Core. Начало
Возможность включать или отключать функции в приложениях без повторного развёртывания кода — мощный инструмент, который можно использовать для быстрой итерации новых функций.

Флаги функций — это метод разработки ПО, который позволяет заключать функции приложения в условный блок, предоставляя переключатель для включения или отключения функции. Главное – возможность переключать функции во время выполнения, т.е. не нужно повторно развёртывать приложение.

Преимущества
1. Возможность развёртывать новые функции в производственных средах в отключённом состоянии. Так мы можем гарантировать, что наша новая функция не повлияет на остальную часть приложения, прежде чем мы включим её для всех пользователей.
2. Канареечное развёртывание — возможность постепенно увеличивать использование новой функции, чтобы гарантировать отсутствие последствий для производительности или каких-либо других ошибок в коде.
3. A/B-тестирование — определение реакции пользователей на новую функцию. Например, новый UI предоставляется только определённой группе пользователей, чтобы увидеть, предпочтут ли они его старому UI.

Виды
1. Логический фильтр – функция включена/выключена.
2. Фильтр по времени - функция активна только между заданным временем начала и окончания.
3. Процентный фильтр - процент определяет вероятность того, что функция включена.
4. Фильтр таргетинга - определяет правила включения функций на основе целевой аудитории.
5. Собственные настраиваемые фильтры.

Использование флагов функций в ASP.NET Core
Для начала добавим пакет Microsoft.FeatureManagement.AspNetCore. Он под капотом управляет жизненным циклом флагов функций. Это даёт нам возможность изменять флаги функций во время выполнения без необходимости перезапуска приложения.

1. Логический фильтр
Добавим фильтр функций в appsettings.json:
{
"FeatureManagement": {
"BoolFeature": true
}
}
NuGet пакет будет по умолчанию искать секцию FeatureManagement. Мы добавили фильтр функции BoolFeature и задали ему значение «включено».

Добавим FeatureManagment в классе Program:
builder.Services.AddFeatureManagement();

Проверим, как это работает:
public class FeatureController : ControllerBase
{
private IFeatureManager featureMgr;
public FeatureController(IFeatureManager fm)
{
featureMgr = fm;
}

public async Task<IActionResult> BoolFeature()
{
if (await featureMgr.IsEnabledAsync("BoolFeature"))
return Ok("Enabled");
else
return BadRequest("Disabled");
}
}

Если перейти на /Feature/BoolFeature, мы увидим результат Ok. Теперь, пока приложение работает, изменим значение BoolFeature на false и повторим запрос. На этот раз мы получим 400 BadRequest.

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

Источник:
https://code-maze.com/aspnetcore-feature-flags/
👍25👎1
День 1652. #ЗаметкиНаПолях
Флаги функций в
ASP.NET Core. Продолжение
Начало

2. Фильтр по времени
Если мы хотим, чтобы функция была включена только в течение определённого временного окна, используем TimeWindowFilter.
Сначала зарегистрируем фильтр:
builder.Services.AddFeatureManagement()
.AddFeatureFilter<TimeWindowFilter>();

В appsettings.json, определим конфигурацию:
"FeatureManagement": {
// …
"TimeFeature": {
"EnabledFor": [
{
"Name": "TimeWindow",
"Parameters": {
"Start": "2023-08-09T09:00:00+03:00",
"End": "2023-08-24T11:00:00+03:00"
}
}
]
}
}
Функция TimeFeature будет включена с 09:00 (Мск) 09.08.2023 до 11:00 (Мск) 24.08.2023. Проверка работы функции аналогична логическому фильтру.

3. Процентный фильтр
Аналогично фильтру по времени, нужно добавить его в сервисы:
 ….AddFeatureFilter<PercentageFilter>();
И в appsettings.json:
"FeatureManagement": {
"PercentFeature": {
"EnabledFor": [
{
"Name": "Percentage",
"Parameters": {
"Value": 50
}
}
]
}
}
Эта функция будет «включена» в 50% случаев.

4. Фильтр таргетинга
Фильтры таргетинга позволяют включить функцию для определённых пользователей или групп. Как обычно, нам нужно зарегистрировать фильтр таргетинга:
….AddFeatureFilter<TargetingFilter>();
И добавить настройку в appsettings.json:
"FeatureManagement": {
"TargetFeature": {
"EnabledFor": [
{
"Name": "Targeting",
"Parameters": {
"Audience": {
"Users": [],
"Groups": [
{
"Name": "Beta Testers",
"RolloutPercentage": 20
}
]
}
}
}
]
}
}
Здесь функция TargetFeature будет доступна для 20% пользователей из группы "Beta Testers". В Users можно задать список Id отдельных пользователей.

Также мы должны добавить логику определения аудитории. Для этого нужно создать сервис реализующий интерфейс ITargetingContextAccessor и зарегистрировать его в DI.
Интерфейс содержит один метод GetContextAsync(), возвращающий ValueTask<TargetingContext>. TargetingContext, в свою очередь, содержит свойства:
- UserId (строка) – сопоставляется со списком Users в appsettings,
- Groups (коллекция строк) – сопоставляется со списком Groups.
Для определения Id или группы для текущего пользователя, в сервис, реализующий ITargetingContextAccessor, можно внедрить IHttpContextAccessor (об этом завтра).

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

Источник:
https://code-maze.com/aspnetcore-feature-flags/
👍8
День 1653.
MOQ-Гейт или Закрытый-Открытый Код

🔥 BRKNG!!!
🔥

Мы прерываем нашу программу… Короче, не мог пройти мимо. Вчера в интернетах разгорелся нешуточный срач вокруг популярнейшего пакета для создания фиктивных объектов в тестах Moq.

Один из пользователей Reddit написал следующий пост:
Я только что обновил Moq в одном из наших проектов и после сборки получил предупреждение о том, что я не установил приложение GitHub Sponsors.
После небольшого расследования выяснилось, что Moq, начиная с версии 4.20, включает в себя анализатор .NET, который сканирует вашу локальную конфигурацию git при сборке, получает ваш email и отправляет его в какой-то сервис в Azure, чтобы проверить, действительно ли вы спонсор.

Потом в GitHub проекта появилась новая ишью:
Похоже, начиная с версии 4.20 в проект добавлена зависимость SponsorLink. Это проект с закрытым кодом, представленный в виде dll с обфусцированным кодом (sic!), который, кажется, сканирует локальные данные (git config?) и отправляет хешированный адрес электронной почты текущего разработчика в облачный сервис. Сканирование предоставляется как инструмент анализатора .NET, который запускается во время сборки. Нет возможности его отключить.
Я могу понять причину этого, но, честно говоря, это довольно страшно с точки зрения конфиденциальности.

Насколько я понял, суть в следующем.
Спонсорство – это фича гитхаба по поддержке разработчиков проектов с открытым кодом. SponsorLink – отдельный пакет от разработчика Moq, работающий как анализатор кода в IDE и проверяющий при сборке проекта, являетесь ли вы спонсором используемого вами пакета с открытым кодом. Если нет, он выдаёт предупреждение компилятора с предложением стать спонсором и ссылкой на спонсорство. Если да, выдаётся благодарственное сообщение. При использовании в терминале или CI/CD, он не делает ничего, таким образом не мешая процессу.

Автор утверждает, что опросил знакомых разработчиков, и идею поддержали, однако лично у меня возникают вопросы по поводу того, так ли это надо. Если в теории за спонсорство анализатор будет «открывать» какие-то новые фишки в пакете (видимо, такая и была задумка на будущее). Впрочем, я отвлёкся.

Однако, как обычно, благими намерениями. Реализация этой проверки спонсорства, собственно, и вызвала бурление. Анализатор (без предупреждения) запускает фоновый процесс, который сканирует параметры вашего git репозитория, находит там email пользователя (по другим данным – все email адреса из истории проекта) и отправляет его по сети в облачный сервис для проверки спонсорства. Вот тут вопросов гораздо больше. В первую очередь это нарушает некоторые законы о защите персональных данных, ведь всё это происходит без согласия и даже без уведомления пользователя. Во-вторых, что там реально происходит, и действительно ли отправляется только email и туда ли, куда говорит автор, посмотреть нельзя, т.к. код самого SponsorLink закрыт. В-третьих, email в настройках git может быть корпоративным и не для публичного распространения. В-четвёртых, даже запретить пакету это делать нельзя.

В общем, получить такую интересную закрытую зависимость, просто обновив широко известный пакет «с открытым исходным кодом» - то ещё удовольствие. Автору справедливо накидали полную панамку… ишьюсов и репортов в NuGet, и проблемная версия Moq 4.20.1 была удалена из репозитория NuGet. Вместо неё вчера появилась версия 4.20.2, в которой заявлено, что ссылка на зависимость от SponsorLink была удалена.

Можете также посмотреть, как Ник Чапсас довольно смешно бомбит с этого.

Источник: https://www.reddit.com/r/dotnet/comments/15ljdcc/does_moq_in_its_latest_version_extract_and_send/
👍32
День 1654. #ЗаметкиНаПолях
Флаги функций в
ASP.NET Core. Продолжение 2
Начало
Продолжение 1

5. Собственные фильтры
Если встроенных фильтров функций недостаточно, у нас есть возможность написать собственные фильтры, реализовав IFeatureFilter. Создадим фильтр, позволяющий использовать функцию только клиентам, у которых установлен определённый язык:
"FeatureManagement": {
"LangFeature": {
"EnabledFor": [
{
"Name": "LangFilter",
"Parameters": {
"AllowedLangs": [
"en-US", "ru-RU"
]
}
}
]
}
}

Мы определяем функцию LangFeature и фильтр LangFilter для неё, добавляя в параметры массив AllowedLangs с языками, для которых функция включена.

Создадим LangFilter:
[FilterAlias(nameof(LangFilter))]
public class LangFilter : IFeatureFilter
{
private IHttpContextAccessor _hCA;
public LangFilter(IHttpContextAccessor hCA)
{
_hCA = hCA;
}
public Task<bool> EvaluateAsync(
FeatureFilterEvaluationContext ctx)
{
var userLang = _hCA
.HttpContext
.Request
.Headers["Accept-Language"]
.ToString();
var settings = ctx
.Parameters
.Get<LangFilterSettings>();

return Task.FromResult(
settings.AllowedLangs
.Any(a => userLang.Contains(a)));
}
}
Сначала внедряем IHttpContextAccessor. Это позволит нам получить доступ к заголовку Accept-Language из запроса пользователя.
Мы реализуем интерфейс IFeatureFilter, который даёт нам метод EvaluateAsync(). В этом методе мы проверяем язык пользователя по параметрам (settings), определённым в appsettings.json, и возвращаем true, если язык есть в списке. Параметры фильтра в строго типизированном виде нам помогает получить класс FeatureFilterEvaluationContext. Создадим простой класс LangFilterSettings для хранения массива AllowedLangs:
public class LangFilterSettings
{
public string[] AllowedLangs { get; set; }
}

Прежде чем протестировать наш фильтр, нужно зарегистрировать в сервисах его и HttpContextAccessor:
builder.Services.AddFeatureManagement()
.AddFeatureFilter<LangFilter>();
builder.Services.AddHttpContextAccessor();

Проверка аналогична проверке логического фильтра:
public async Task<IActionResult> LangFeature()
{
if (await featureMgr.IsEnabledAsync("LangFeature"))
return Ok("Enabled");
else
return BadRequest("Disabled");
}

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

Источник:
https://code-maze.com/aspnetcore-feature-flags/
👍8
День 1655. #ЗаметкиНаПолях
Флаги функций в
ASP.NET Core. Окончание
Начало
Продолжение 1
Продолжение 2

Другие способы проверки флагов функций
Интерфейс IFeatureManager и блок if-else - не единственный метод проверки флагов функций. Вот несколько других.

1. Атрибут FeatureGate
В ASP.NET Core есть возможность помечать конечные точки или контроллеры атрибутом флага функции FeatureGate:
[FeatureGate("BoolFeature")]
public IActionResult BoolFeature()
{ … }
Здесь конечная точка BoolFeature помечена атрибутом флага функции BoolFeature (имена не обязательно должны совпадать). Если функция отключена, при обращении к этой конечной точке мы получим 404 NotFound. Это код ответа по умолчанию, но поведение можно настроить, создав обработчик отключённых функций, реализующий IDisabledFeaturesHandler.
Кроме того, в атрибуте можно указывать несколько функций через запятую, а также задать, должны ли они быть включены все либо хотя бы одна с помощью перечисления RequirementType.

2. Промежуточное ПО
Если наша функция более сложная и затрагивает промежуточное ПО приложения, мы можем условно добавить её с помощью флагов функций:
app.UseMiddlewareForFeature<MyMiddlewareFeature>(
"MyMiddlewareFeatureFlag");

При этом MyMiddlewareFeature будет добавлена в конвейер только в том случае, если MyMiddlewareFeatureFlag включен.

3. Представления MVC
Если в приложении MVC нам нужно условно показать или скрыть фрагмент пользовательского интерфейса в зависимости от состояния флага функции, ASP.NET Core предоставляет для этого простой способ:
<feature name="MyUIFeature">
<p>Отображается, если MyUIFeature включена.</p>
</feature>
<feature name="MyUIFeature" negate="true">
<p>Отображается, если MyUIFeature отключена.</p>
</feature>
<feature name="FeatureA, FeatureB" requirement="All">
<p>Отображается, если обе функции включены.</p>
</feature>

Для этого надо добавить тэг-хелпер в файл _ViewImports.cshtml:
@addTagHelper *, Microsoft.FeatureManagement.AspNetCore

4. Azure App Configuration
Когда мы развёртываем приложение, использовать appsettings.json - не самый идеальный сценарий. Вместо этого мы можем использовать что-то вроде Azure App Configuration для независимого управления флагами функций. Это предпочтительно, поскольку нам предоставляется пользовательский интерфейс для управления функциями, и это означает, что не нужно вручную изменять файл appsettings.json приложения.

Источник: https://code-maze.com/aspnetcore-feature-flags/
👍9
DotNext 2023 — крупнейшая в России конференция для .NET-разработчиков

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

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

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