.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
День 1763. #ЗаметкиНаПолях
Оптимизируем Запросы EF Core.
Продолжаем разговор о быстродействии запросов в EF Core. Существует множество правил оптимизации запросов, сегодня рассмотрим основные.

1. Выбирайте только нужные поля
Рассмотрим пример:
var employees = context.Employees
.Select(e => new EmployeeDto
{
Name = e.Name,
Email = e.Email
}).ToList();

В БД скорее всего будет больше 2 полей. Но нам не нужны остальные.

Преимущества

- Сокращает объём данных, передаваемых из БД в приложение, что поможет, особенно в распределенных средах.
- Снижает использование памяти приложения.
- БД может возвращать результат быстрее, например, за счёт покрывающих индексов.
- Уменьшаются накладные расходы на отслеживание изменений.

2. Избегайте N+1 запросов
Это происходит, когда ваше приложение выполняет один запрос для получения набора объектов, а затем дополнительные запросы для каждого объекта для получения связанных данных:
// Получаем все блоги - 1 запрос
var blogs = context.Blogs.ToList();
foreach (var b in blogs)
{
// Для каждого блога получаем посты
// N запросов
var posts = b.Posts;
foreach (var p in posts)
Console.WriteLine(p.Title);
}

Вместо этого используем «жадную загрузку»:
var blogs = context.Blogs
.Include(b => b.Posts).ToList();
foreach (var b in blogs)
{
foreach (var p in b.Posts)
Console.WriteLine(post.Title);
}

«Жадная» загрузка с помощью Include(b => b.Posts) сообщает EF Core загрузить посты, связанные с каждым блогом, как часть изначального запроса. Таким образом выполняется один запрос, который извлекает все блоги со связанными с ними постами. Поэтому во время итерации в цикле foreach не выполняются никакие дополнительные запросы.

Преимущества
- Значительное снижение нагрузки на БД и сеть (особенно при грамотной организации данных: индексы, внешние ключи).
- Упрощение логики кода, поскольку в нём явно указано намерение загрузить связанные данные.

3. Используйте AsNoTracking()
Этот метод особенно полезен, когда вы только считываете данные из базы. В следующем примере список продуктов извлекается без затрат на отслеживание изменений:
var products = context.Products
.AsNoTracking().ToList();
// Используем products только для чтения

Преимущества
- Особенно заметно в крупных приложениях или при работе с большими наборами данных. Запросы выполняются быстрее и потребляется меньше памяти.
- Сокращение накладных расходов, т.к. не требуется отслеживать изменения или сохранять информацию о состоянии объектов.

4. Используйте разделённые запросы с помощью AsSplitQuery().
Подробности здесь.

Источник: https://stefandjokic.tech/blog
👍14
День 1764. #ЗаметкиНаПолях #Debugging
Правила отладки: Меняйте За Раз Что-то Одно

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

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

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

Изолируйте ключевой фактор
Суть эффективной отладки в выявлении критического фактора путем сужения фокуса до определённого раздела кода, в котором может возникнуть потенциальная проблема. Так процесс отладки становится более точным и эффективным. Чтобы сделать это систематически исключайте части программы и наблюдайте, сохраняется ли ошибка. Нет ли при этом знакомой закономерности? Признание того, что «я это видел раньше», часто означает начало понимания, если не полное решение.

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

Сравните плохое с хорошим
Изучая два сценария сбоя и успеха, сравните различные аспекты: трассировки, выходные данные отладки и окна состояния и т.п. Если между двумя тестами произошло множество изменений кода или созданы разные сценарии работы, их сравнение может стать затруднительным. Чтобы изолировать ошибку, важно свести к минимуму различия между ними. Если в неудачном тесте есть что-то, чего нет в удачном, вы на пути к обнаружению проблемы.

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

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

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

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

Источник: https://dev.to/rajasegar/debugging-rules-change-one-thing-at-a-time-3kc6
👍6
День 1765. #ВопросыНаСобеседовании #Многопоточность
Самые часто задаваемые вопросы на собеседовании по C#


27. Опишите концепцию конфликта блокировок в многопоточности и объясните его влияние на производительность приложения. Как можно решить и смягчить проблемы, связанные с конфликтами блокировок?

Конфликт блокировок — это состояние, когда один поток ожидает другого, пытаясь получить блокировку. Какое бы время ни было потрачено на ожидание блокировки, это «потерянное» время, потраченное впустую. Очевидно, что это может вызвать серьезные проблемы с производительностью. Конфликты блокировок могут быть вызваны любым типом механизма синхронизации потоков. Это может быть из-за оператора блокировки, AutoResetEvent/ManualResetEvent, ReaderWriterLockSlim, Mutex или Semaphore и всех остальных. Всё, что заставляет один поток приостанавливать выполнение до тех пор, пока не поступит сигнал другого потока, может вызвать конфликт блокировок.

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

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

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

3. Используйте структуры данных и алгоритмы без блокировки
Если возможно, используйте неблокирующие алгоритмы и структуры данных, которые не полагаются на блокировки, такие как ConcurrentQueue, ConcurrentDictionary или ConcurrentBag.

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

5. Минимизируйте конфликты с помощью секционирования
Разделите данные на секции, обрабатываемые отдельными потоками, что снизит необходимость синхронизации.

6. Избегайте вложенных блокировок
Уменьшите риск взаимоблокировок и конфликтов, избегая вложенных блокировок или иерархий блокировок.

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

Источник: https://dev.to/bytehide/c-multithreading-interview-questions-and-answers-4opj
👍24
This media is not supported in your browser
VIEW IN TELEGRAM
День 1766. #ЧтоНовенького
Пишем Сообщения Коммитов с Помощью GitHub Copilot

В последней превью версии Visual Studio добавлена новая функция –автоматическое описание изменений в сообщении коммита.

Чтобы опробовать новую функцию, загрузите последнюю превью версию Visual Studio и обновите расширение GitHub Copilot Chat Extension. Вам также понадобится активная подписка GitHub Copilot.

Новая функция Generated Commit Message (Сгенерированное Сообщение Коммита) использует GitHub Copilot для описания изменений вашего кода. Благодаря этому написание описательных и полезных сообщений о коммитах становится таким же простым, как нажатие кнопки и последующее добавление пояснений.

Используйте новый значок ручки в окне Git Changes, чтобы сгенерировать предложение. GitHub Copilot проверит изменения файлов в вашем коммите, обобщит их, а затем опишет каждое изменение. См. видео. Затем вы можете использовать предложение или отменить его, а также добавить свои комментарии.

Источник
👍9
День 1767. #Оффтоп
А вы знали, что даже в таком, казалось бы, простом алгоритме, как бинарный поиск, может быть баг? Причём, не где-нибудь, а в реализации в Java. И просуществовал он там почти 10 лет!

Об этом в новом видео на канале Computerphile.

А если интересно узнать, либо вспомнить, собственно алгоритм, то объяснение здесь.
👍6
День 1768. #Карьера
Пишем Код, как Сеньор

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

1. Завершите работу, прежде чем двигаться дальше.
Часто вы можете оказаться в ситуации, когда время поджимает, работа на 90% готова, и возникает желание отложить оставшееся на потом. Но так вы накапливаете для себя технический долг. Возможно, только вы об этом знаете, но рано или поздно другие тоже узнают. Лучше честно сказать, что вы ещё не закончили. Если руководство разочаровано, пусть лучше они знают реальное состояние проекта вместо того, чтобы ваша ложь выяснилась на более поздних этапах. Кто-то другой будет просматривать ваш код в будущем. И вас будут оценивать, глядя на ваш код. Лучше убедиться, что вы действительно закончили работу – это одна из главных отличительных черт сеньора.

2. Соблюдайте стандарты кодирования.
Сегодня большинство IDE могут форматировать код (например, editorconfig для Visual Studio). Если в вашей IDE этого нет, найдите утилиту или плагин. Ничто не разочаровывает больше в кодовой базе, чем обнаружить разные стили написания кода в разных местах. Имеет смысл создать стандарт для команды, либо использовать какой-то готовый, и договориться его придерживаться.

3. Дисциплинированно документируйте шаблоны.
Многие команды, начиная проект, договариваются о наборе шаблонов/библиотек и приступают к работе. Не полагайтесь на самодокументирующийся код, а потратьте время на фактическую документацию шаблонов и решений, которые вы приняли в проекте. Тогда, если вы при обзоре кода обнаружите, что разработчик использует другой шаблон, у вас не будет дебатов на тему, нужен он или нет, - вы сможете сослаться на документ. Также, если вы используете шаблон, который уже является частью фреймворка или языка, добавьте ссылку на документацию, где объясняется, как его использовать, чтобы остальные не гуглили, а имели именно тот пример, который использовали вы. Если вы собираетесь добавить новый шаблон, сначала обсудите это с командой. Возможно, кто-то раньше его уже рассматривал, и отказался от него по каким-то причинам. Не останавливайтесь на устном обсуждении. Задокументируйте цель шаблона, когда его использовать, а когда нет, и несколько примеров кода его применения в распространённых сценариях.

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

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

Источник:
https://youtu.be/oJbfMBROEO0
👍15👎1
День 1769. #ЗаметкиНаПолях
Используем Перехватчики в EF Core

Перехватчики в EF Core позволяют перехватывать, изменять или подавлять операции EF Core.

Перехватчики регистрируются для каждого экземпляра DbContext при настройке контекста. Каждый перехватчик реализует интерфейс IInterceptor. Несколько распространённых производных интерфейсов включают
- IDbCommandInterceptor,
- IDbConnectionInterceptor,
- IDbTransactionInterceptor,
- ISaveChangesInterceptor.

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

Добавление записей аудита
Записи аудита изменений сущностей - ценная функция в некоторых приложениях. Вы записываете дополнительную информацию аудита каждый раз, когда объект создаётся или изменяется. Также это могут быть значения «до» и «после», в зависимости от ваших требований.
Например, создадим интерфейс IAuditable с датами создания и изменения объекта:
public interface IAuditable
{
DateTime Created { get; }
DateTime? Modified { get; }
}

Добавим UpdateInterceptor для записи значений аудита. Он использует ChangeTracker для поиска всех экземпляров IAuditable и устанавливает соответствующее значение свойства. Здесь мы используем метод SavingChangesAsync, который запускается до того, как изменения будут сохранены в БД.
internal sealed class UpdateInterceptor 
: SaveChangesInterceptor
{
public override
ValueTask<InterceptionResult<int>>
SavingChangesAsync(
DbContextEventData e,
InterceptionResult<int> result,
CancellationToken ct = default)
{
if (e.Context is not null)
UpdateEntities(e.Context);

return base
.SavingChangesAsync(e, result, ct);
}

private static void
UpdateEntities(DbContext ctx)
{
var now = DateTime.UtcNow;
var entities = ctx
.ChangeTracker
.Entries<IAuditable>()
.ToList();

foreach (var e in entities)
{
if (e.State == EntityState.Added)
e.Property(
nameof(IAuditable.Created)) = now;

if (e.State == EntityState.Modified)
e.Property(
nameof(IAuditable.Modified)) = now;
}
}
}

Эту реализацию можно легко расширить, включив в неё, например, информацию о текущем пользователе.

Зарегистрировать перехватчик можно следующим образом:
services.AddSingleton<UpdateInterceptor>();
services.AddDbContext<
IApplicationDbContext,
AppDbContext>(
(sp, opts) => opts
.UseSqlServer(connString)
.AddInterceptors(
sp.GetRequiredService<UpdateInterceptor>());


Источник: https://www.milanjovanovic.tech/blog/how-to-use-ef-core-interceptors
👍18
День 1770. #ЗаметкиНаПолях #AsyncAwaitFAQ
ConfigureAwait в .NET 8. Начало

Мы привыкли использовать ConfigureAwait(false), чтобы предотвратить захват контекста. Изначально его рекомендовали использовать везде, но со временем рекомендация поменялась на «используйте его в коде библиотек, но не в коде приложения». Это довольно простое правило. Недавно (в частности, из-за того, что SynchronizationContext был удалён из ASP.NET Core), многие решили вовсе отказаться от ConfigurationAwait(false), даже в коде библиотек.

Есть несколько распространенных заблуждений относительно ConfigureAwait(false):
1. Это не лучший способ избежать взаимоблокировок. Это не его цель. Чтобы избежать взаимоблокировок, необходимо убедиться, что весь асинхронный код использует ConfigureAwait(false), включая код в библиотеках и среде выполнения. Да и просто это не очень удобное в обслуживании решение.

2. ConfigureAwait настраивает ожидание, а не задачу. Например, функция ConfigureAwait(false) в
SomethingAsync().ConfigureAwait(false).GetAwaiter().GetResult() 

ничего не делает. Аналогично, ожидание в
var task = SomethingAsync();
task.ConfigureAwait(false);
await task;

по-прежнему продолжает использовать захваченный контекст, полностью игнорируя ConfigureAwait(false).

3. ConfigureAwait(false) не означает «запустить остальную часть этого метода в потоке из пула потоков» или «запустить остальную часть этого метода в другом потоке». Он работает только в том случае, если await передаёт управление, а затем возобновляет асинхронный метод. В частности, await не передаст управление, когда задача уже завершена, ConfigureAwait не будет иметь никакого эффекта, поскольку ожидание продолжится синхронно.

Теперь посмотрим, что изменилось в ConfigureAwait в .NET 8.
Ничто из существующего поведения не изменилось. Поведение по умолчанию то же, и ConfigurationAwait(false) имеет то же поведение. Добавлена новая перегрузка.

ConfigureAwait(ConfigureAwaitOptions)
ConfigurationAwaitOptions — это новый тип, который предоставляет все возможные способы настройки ожидаемых объектов:
[Flags]
public enum ConfigureAwaitOptions
{
None = 0x0,
ContinueOnCapturedContext = 0x1,
SuppressThrowing = 0x2,
ForceYielding = 0x4,
}


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

Во-вторых, пока это доступно только в Task и Task<T>, по крайней мере, для .NET 8. В ValueTask/ValueTask<T> это пока не добавлено.

Далее рассмотрим каждую из опций.

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

Источник:
https://blog.stephencleary.com/2023/11/configureawait-in-net-8.html
👍12👎2
День 1771. #ЗаметкиНаПолях #AsyncAwaitFAQ
ConfigureAwait в .NET 8. Окончание

Начало

None и ContinueOnCapturedContext
Эти опции очевидны, с небольшим замечанием.
ContinueOnCapturedContext — то же самое, что true, т.е. ожидание захватит контекст и возобновит выполнение асинхронного метода в этом контексте.
None — то же самое, что false, т.е. ожидание не будет захватывать контекст.
Замечание. По умолчанию контекст НЕ захватывается. Если явно не добавить ContinueOnCapturedContext, контекст не будет захвачен.
Поведение по умолчанию не изменилось: отсутствие ConfigureAwait аналогично true или ContinueOnCapturedContext. Но при использовании других опций ConfigureAwaitOptions, для захвата контекста нужно будет добавлять флаг ContinueOnCapturedContext (см. ниже).

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

Это скорее всего будет полезно при отмене задач. Иногда нужно вызвать отмену задачи, дождаться её, а затем выполнить другую задачу вместо отменённой. Тогда отменяемую задачу можно ожидать с SuppressThrowing, и метод продолжит работу после завершения задачи, независимо от того, была ли она успешной, отменённой или вызвала исключение.

Замечание: эта семантика не работает для Task<T>, поскольку в этом случае ожидание должно возвращать значение типа T. Неясно, какое значение будет уместно вернуть в случае игнорируемого исключения, поэтому текущее поведение заключается в выдаче исключения ArgumentOutOfRangeException во время выполнения. Было добавлено новое предупреждение:
CA2261: The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task (ConfigureAwaitOptions.SuppressThrowing поддерживается только с необобщённым Task).
Это предупреждение лучше сделать ошибкой, поскольку во время выполнения оно всегда будет давать сбой.

ForceYielding
ForceYielding – флаг, который может быть полезен в очень редких случаях, но в этих случаях он будет очень удобен. Он похож на Task.Yield. Yield возвращает специальную ожидаемую задачу, которая всегда утверждает, что она не завершена, но немедленно планирует продолжение. Обычно await проверяет, завершена ли ожидаемая задача и, если да, продолжает синхронно. ForceYielding предотвращает такое синхронное поведение, заставляя ожидание вести себя асинхронно. Это может быть полезно, например, при модульном тестировании.

Обратите внимание, что сам по себе флаг ForceYielding также подразумевает отказ от продолжения работы с захваченным контекстом, поэтому это то же самое, что сказать «запланировать остальную часть этого метода для выполнения в пуле потоков» или «переключиться на поток из пула потоков».
Task.Yield возобновит работу в захваченном контексте, поэтому это не то же самое, что ForceYielding. Task.Yield аналогично комбинации флагов ForceYielding | ContinueOnCapturedContext.

Конечно, реальная ценность ForceYielding в том, что его можно применить к любой задаче. Раньше приходилось либо добавлять отдельный await Task.Yield(); либо создавать собственный ожидаемый объект. Теперь в этом больше нет необходимости, поскольку ForceYielding можно применять к любой задаче.

Источник: https://blog.stephencleary.com/2023/11/configureawait-in-net-8.html
👍9
День 1772. #ЗаметкиНаПолях #PatternMatching
Шпаргалка по Сопоставлению по Шаблону в C#

Сопоставление по шаблону — это функция, используемая для проверки выражений на соответствие некоторым условиям с одновременной проверкой их типов. Исторически оно было отличительной чертой функционального программирования, и уже существует в других популярных языках, таких как Scala, Rust, Python, Haskell, Prolog и многих других. Сопоставление по шаблону было представлено в C# 7, и с тех пор получало множество обновлений в последующих версиях.

Помните, что его можно применять только в выражениях is или switch.

С# 7
Шаблон типа

Проверка типа выражения
public bool IsFood(object product)
=> product is Food;


Шаблон объявления
Проверка типа + присвоение переменной при успехе.
public bool IsFridgeFood(object product)
=> product is Food food &&
RequiresFridge(food.StorageTemp);


Шаблон константы
Проверка на константное значение: int, float, char, string, bool, enum, const, null.
public bool IsFresh(Food food)
=> food?.Category?.ID is (int)Category.Fresh;


Шаблон null
Проверка ссылочного или обнуляемого типа на null
public bool DoesNotExist(Food food)
=> food is null;


Шаблон var
Подобно шаблону типа, шаблон var сопоставляет шаблон, проверяет на null и присваивает значение переменной. Тип var объявляется на основе типа времени компиляции соответствующего выражения.
public bool RequiresFridge(Food food)
=> GetStorageRequirement(food) is var req &&
req is StorageRequirement.Freezer;


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

Источник:
https://codingsonata.com/your-quick-guide-to-pattern-matching-in-c/
👍11
День 1773. #ЗаметкиНаПолях #PatternMatching
Шпаргалка по Сопоставлению по Шаблону в C#. Продолжение

C# 7

С# 8
Шаблон свойства

Вы можете включать в шаблон элементы объекта вместо переменных, для соответствия заданным условиям. Его можно легко использовать с другими типами шаблонов для создания гибких и мощных логических выражений.
public bool IsOrganic(Food food)
{
return food is
{
NoGMO: true,
NoFertilizers: true,
NoPesticides: true
};
}

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

Шаблон отказа
Оператор отказа (discard) _ в шаблоне соответствует всему, включая значение null. Его использование проявляется в новых выражениях switch, чтобы соответствовать выбору по умолчанию. В примере ниже, если вид хранения продуктов не указан или не соответствует ни одному варианту, будет выдано исключение:
public int GetStorageTemp(
StorageRequirement req)
=> req switch
{
StorageRequirement.Freezer => -18,
StorageRequirement.Fridge => 4,
StorageRequirement.RoomTemp => 25,
_ => throw new
InvalidStorageRequirementException()
};


Позиционный шаблон
В основном используется со структурами, использует деконструктор для сопоставления шаблона в соответствии с позицией значений в деконструкторе. В примере ниже Price – структура со значением и валютой. Здесь используется шаблон отказа, чтобы игнорировать валюту, когда цена 0.
public bool IsFree(Food food)
=> food.Price is (0, _);


Шаблон кортежа
Особый вариант позиционного шаблона, позволяющий проверять несколько свойств класса в одном выражении.
public string GetDescription(Food food) 
=> (food.NonGMO, food.Category.ID) switch
{
(true, (int)Category.Fresh)
=> "Non-GMO Fresh Product",
(true, (int)Category.Dairy)
=> "Non-GMO Dairy",
(false, (int)Category.Meats)
=> "GMO Meat!",
(_, _) => "Invalid Food Group"
};


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

Источник:
https://codingsonata.com/your-quick-guide-to-pattern-matching-in-c/
👍15
День 1774. #ЗаметкиНаПолях #PatternMatching
Шпаргалка по Сопоставлению по Шаблону в C#. Продолжение

С# 7
С# 8

C# 9
«Расширенный» шаблон типа

Позволяет выполнять проверку типов в выражениях switch.
public string CheckValueType(object value) 
=> value switch
{
int => "integer",
decimal => "decimal",
double => "double",
_ => "not a number"
};


Логический шаблон
Представляет собой совокупность отрицания (not), конъюнкции (and) и дизъюнкции (or). Вместе они называются комбинаторами шаблонов. Они используются для объединения шаблонов и применения к ним логических условий.
public bool RequiresFrige(Category cat)
=> cat is Category.Dairy or Category.Meats;


Шаблон отношения
Позволяет применять реляционные операторы > < >= <= для сопоставления шаблонов с константами или перечислениями.
public StorageRequirement 
GetStorageRequirement(Food food)
=> food.StorageTemp switch
{
<= -18 => StorageRequirement.Freezer,
>= 2 and < 6 => StorageRequirement.Frige,
> 6 and < 30 => StorageRequirement.RoomTemp,
_ => throw new
InvalidStorageRequirementException(
food.StorageTemperature)
};


Шаблон отрицания null
Проверяет выражение на ненулевое значение
public bool Exists(Blog blog)
=> blog is not null;


Скобки в шаблоне
Круглые скобки в шаблоне используются для управления порядком выполнения и группировки логических выражений. Их можно использовать в любом типе шаблона, но в основном это связано с использованием комбинаторов шаблонов.
public bool RequiresFridge(int storageTemp)
=> storageTemp is > 1 and (< 6);


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

Источник:
https://codingsonata.com/your-quick-guide-to-pattern-matching-in-c/
👍17
День 1775. #ЗаметкиНаПолях #PatternMatching
Шпаргалка по Сопоставлению по Шаблону в C#. Окончание

С# 7
С# 8
C# 9

C# 10
Расширенный шаблон свойств

В C# 10 была решена проблема соответствия синтаксиса вложенных свойств. С появлением расширенного шаблона свойств синтаксис использования вложенных свойств при сопоставлении с шаблоном стал понятным и кратким.
public bool RequiresFridge(Food food)
=> food is
{
Category.ID: (int)Category.Dairy or
(int)Category.Meats
};


C# 11
Шаблон списка

Позволяет сопоставить список или массив с набором последовательных элементов. Вы можете комбинировать его с шаблонами отказа, комбинаторами шаблонов, диапазонами, переменными и типом назначения, чтобы создать очень гибкое и мощное сопоставление по шаблону списка.
public (int?, int?) FindOneAndFour()
{
int[] numbers = { 1, 2, 3, 4, 5 };

// Совпадает, если
// - 2й элемент любой
// - 3й больше или равен 3
// - 5й равен 5
if (numbers is
[var one, _, >= 3, int four, 5])
{
return (one, four);
}

return (null, null);
}

См. подробнее про шаблоны списков.

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

Источник: https://codingsonata.com/your-quick-guide-to-pattern-matching-in-c/
👍16
День 1776. #юмор
👍30👎2
День 1777. #ЗаметкиНаПолях
Где Имеет Смысл Применять Первичные Конструкторы
Первичные конструкторы классов появились в C# 12. Один из первых вопросов, который может возникнуть: как они работают и какова область их применения?

Во-первых, параметры первичных конструкторов ведут себя как параметры метода или обычного конструктора. Т.е. к этому параметру можно получить доступ и изменить его, как и любой другой параметр, из любого места внутри класса. Например:
public class Customer(string name)
{
// выдаст предупреждение компилятора
public string Name { get; } = name;

public string FullName => name;
public void Format() => name = name.ToUpper();
}

var c = new Customer("steve");
c.Format();
Console.WriteLine(c.Name); // steve
Console.WriteLine(c.FullName); // STEVE

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

Валидация
Как правило, рекомендуется проверять аргументы конструктора. Типичное место проверки входных данных — конструктор с использованием защитных предложений.
Примечание: я не нашёл, как можно проверять собственно параметры первичного конструктора. Поэтому подозреваю, что это придётся делать при каждом использовании, например:
public class Customer(string name)
{
public string Name
{
get
{
ArgumentException
.ThrowIfNullOrEmpty(nameof(name));
return name;
}
}
}

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

Внедрение зависимостей
Вы можете использовать первичные конструкторы для зависимостей, как и обычный конструктор:
public class MyService(
IMyRepo _repo,
ILogger<MyService> _logger)
{

}

В небольшом простом классе сервиса может быть приемлемо использовать аргументы первичного конструктора. Однако это быстро может перерасти в проблему.

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

Во-вторых, по мере роста объёма кода класса становится неочевидно, откуда берутся параметры класса. Они называются как закрытые поля полями (с префиксом _), но ими не являются. Если вы попытаетесь получить к ним доступ через this, это не сработает.

DTO
Конечно, это будет работать. Но вам всё равно захочется назначить их свойствам. Однако в этом случае проще использовать запись, которая даст и неизменяемость, и переопределённый ToString, и прочие преимущества.

Итого
Учитывая текущую реализацию первичных конструкторов классов C#:
- Используйте аргументы первичного конструктора только для инициализации полей и свойств. Избегайте доступа к ним где-либо ещё (за исключением, возможно, тривиальных реализаций классов).
- Учитывая, что это параметры, их лучше называть, как и обычные параметры с помощью camelCase, без префикса _.
- До тех пор, пока не будут доступны дополнительные ограничения для параметров первичного конструктора, они представляют собой ружьё, которым разработчики могут отстрелить себе …, если не будут осторожны. Обязательно включите TreatWarningsAsErrors, чтобы отслеживать любые случаи, когда вы ссылаетесь на параметры уровня класса, помимо инициализации (особенно предупреждение CS9124).

Источник: https://blog.nimblepros.com/blogs/where-csharp-primary-constructors-make-sense/
👍14
День 1778. #ЧтоНовенького
Новый Способ Логирования в .NET 8
В последней версии .NET представлен новый, более гибкий и усовершенствованный, способ ведения журнала – атрибут LoggerMessageAttribute.

Вот старый способ:
public class MyRepo
{
public async Task Delete(
IEnumerable<string> ids)
{
for (var i = 0; i < totalBatches; i++)
{

_logger.LogDebug(
"Deleted Batch {BatchNumber} - {TotalDeleted} elements.",
i+1, (i+1)*batchSize);
}
}
}


В .NET 8 атрибут LoggerMessageAttribute получил некоторые расширения, которые помогут вам сделать то же самое с помощью генераторов исходного кода:
public partial class MyRepo
{
public async ValueTask DeleteBulkAsync(IEnumerable<string> ids)
{
for (var i = 0; i < totalBatches; i++)
{

LogDelete(i+1, (i+1)*batchSize);
}
}

[LoggerMessage(LogLevel.Debug,
"Deleted Batch {BatchNumber} - {TotalDeleted} elements")]
private partial void LogDelete(
int batchNumber, int totalDeleted);
}


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

Подробнее об атрибуте LoggerMessageAttribute можно почитать в официальном анонсе .NET 8 превью 6.

Источник: https://steven-giesel.com/blogPost/1add3827-4233-4e31-9ce9-bcc34d64e76f
👍15👎1
День 1779. #ЗаметкиНаПолях
Сравниваем Алгоритмы Ограничения Обработки Запросов. Начало

Всегда следует устанавливать ограничение на количество входящих запросов. Иначе система может оказаться уязвимой для злоумышленников. Рассмотрим 4 основных алгоритма ограничения обработки запросов.

Ограничение обработки запросов (Rate Limiting) — это способ запретить клиентам слишком часто обращаться к системе.

Преимущества:
1. Защищает систему от DoS атак
Если злоумышленник попытается повлиять на систему, вызывая API так часто, что вся система выйдет из строя, ограничение поможет уменьшить количество выполняемых операций.
2. Добавляет уровень безопасности, предотвращающий атаки грубой силой.
Если злоумышленник попытается получить доступ к системе, перепробовав все возможные пароли, политика ограничения обработки запросов не позволит ему выполнить слишком много попыток.
3. Защищает медленно работающую часть системы.
Если часть системы не может быстро обработать запрос, клиент может добавить политику повтора запроса. Так он может перегрузить и без того плохо работающий компонент, что сделает его совершенно неспособным обрабатывать любые запросы.

Чтобы клиенты знали, что их запросы не обрабатываются из-за слишком частых попыток, нужно использовать код ответа HTTP: 429 Too Many Requests. Так клиенты могут реализовать логику повторных попыток, учитывая это. Ответ также должен включать HTTP-заголовок Retry-After, чтобы сообщить клиенту, как долго ждать перед выполнением следующего запроса.

Теперь рассмотрим стратегии ограничения количества запросов.

1. Фиксированное окно
Ограничивает количество запросов, разрешённых в течение данного временного окна. Временные рамки определяются сервером и одинаковы для всех клиентов.

Допустим, мы можем принимать 100 запросов в минуту. По прошествии минуты, сможем принять ещё 100. Можно выбрать два типа ограничения:
- на уровне пользователя позволяет каждому пользователю выполнять, в нашем примере, 100 запросов в минуту,
- на уровне сервера означает, что сервер способен обрабатывать всего 100 запросов в минуту от всех клиентов.

Алгоритм прост в реализации, но имеет некоторые недостатки:
1) Может допускать всплески запросов в начале или в конце каждого окна, что может перегрузить систему.
2) Предположим, что многие запросы переводятся на следующую минуту. В этом случае вы в итоге добавляете всё больше запросов в начало следующей минуты, вызывая всплеск запросов, которые система может быть не в состоянии обработать.

2. Скользящее окно
Делит время на фиксированные интервалы, и каждый интервал начинается с первого запроса клиента. Если окно составляет 100 запросов в минуту, то:
- Клиент А делает первый запрос в 09:00:05 и может обратиться к системе 100 раз до 09:01:05.
- Клиент B - первый запрос в 09:00:38 - 100 запросов до 09:01:38.
Так оба клиента могут обращаться к системе по 100 раз в минуту, но их временные окна не зависят друг от друга и могут пересекаться.

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

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

Источник:
https://www.code4it.dev/architecture-notes/rate-limiting-algorithms/
👍13
День 1780. #ЗаметкиНаПолях
Сравниваем Алгоритмы Ограничения Обработки Запросов. Окончание

Начало

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

Этот алгоритм обеспечивает постоянство потока запросов и уменьшает перегрузку. Если запросы добавляются быстрее, чем могут быть обработаны, лишние запросы отбрасываются.

Алгоритм может быть реализован с использованием очереди FIFO (First In, First Out). В очереди хранится список запросов, и фиксированное количество запросов извлекается из очереди с постоянной скоростью, а затем обрабатывается.

Например: каждый запрос заполняет одну ячейку в ведре, и одна ячейка извлекается через постоянные промежутки времени. Если размер ведра 100 запросов, а скорость утечки — 5 запросов в секунду, то, если будет поступать более 5 запросов в секунду, ведро заполнится, а входящие запросы будут блокироваться до тех пор, пока не освободится место.

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

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

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

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

См. также «Промежуточное ПО для ограничений в .NET 7»

Источник: https://www.code4it.dev/architecture-notes/rate-limiting-algorithms/
👍4
День 1781. #ЗаметкиНаПолях
Доброго зимнего субботнего утра всем. Не желаете ли немного Кавки на завтрак?)))

Да, вот такой у меня сегодня юмор. И нет, я не опечатался в фамилии. Я говорю о Ернеи Кавке, разработчике из Словении. Не так давно он выступил на конференции NDC Oslo с докладом «Common mistakes in EF Core», который я сегодня и хочу вам предложить посмотреть.

Для опытных разработчиков, давно работающих с Entity Framework, он, конечно, не откроет Америки. Ошибки, которые он рассматривает, довольно хорошо известны. Однако, если вам не давал покоя вопрос, насколько же хуже может быть, например, использование IEnumerable вместо IQueriable, то Ерней заморочился с тестами и покажет это наглядно.

В общем, приятного аппетита просмотра.
👍13