.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
День 1782. #ЧтоНовенького
Помечаем Экспериментальные API Атрибутом Experimental

При написании библиотек и фреймворков, которые используют другие, иногда нужно сообщить клиентам, что данный API по-прежнему считается «экспериментальным». Например, предоставить другим возможность работать с частью кода, оставив для себя возможность что-то ломать. В C# 12 это можно сделать с помощью ExperimentalAttribute.

Например, в JetBrains Space SDK есть метод MapSpaceAttachmentProxy, который пока является экспериментальной функцией:
using System.Diagnostics.CodeAnalysis;

public static class SpaceMapAttachmentProxyExtensions
{
[Experimental("SPC101")]
public static IEndpointConventionBuilder
MapSpaceAttachmentProxy(
this IEndpointRouteBuilder endpoints,
string path)
{
//…
}
}

Сборка проекта, использующего этот метод, по умолчанию завершается неудачей с ошибкой “Error SPC101: ‘MapSpaceAttachmentProxy(…)’ is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.” (Ошибка SPC101: 'MapSpaceAttachmentProxy(…)' предназначен только для ознакомительных целей и может быть изменён или удалён в будущих обновлениях. Подавите эту диагностику, чтобы продолжить.)

Также с помощью свойства UrlFormat в атрибут можно добавить URL, где люди смогут найти дополнительную информацию об API. Здесь заместитель {0} MSBuild заменит диагностическим идентификатором:
[Experimental("SPC101", UrlFormat = "https://www.example.com/diagnostics/{0}.html")]


Вы можете подавить диагностику экспериментальных атрибутов в файле проекта, добавив свойство <NoWarn>:
<Project Sdk="Microsoft.NET.Sdk.Web">
<!-- ... -->
<PropertyGroup>
<NoWarn>SPC101</NoWarn>
</PropertyGroup>
</Project>

Либо с помощью директивы pragma в коде:
#pragma warning disable SPC001


Что делать в старых версиях языка?
Как вариант, можно использовать атрибут Obsolete. Хотя он по умолчанию отображается только как предупреждение, но, по крайней мере, это будет видно в журнале сборки. Начиная с .NET6, вы также можете добавить к атрибуту диагностический идентификатор, давая людям возможность подавить сообщение, если они согласны использовать этот экспериментальный API:
[Obsolete("'MapSpaceAttachmentProxy(...)' is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to remove this warning.", DiagnosticId = "SPC101")]


Источник: https://blog.maartenballiauw.be/post/2023/11/08/opt-in-to-experimental-apis-using-csharp-12-experimentalattribute.html
👍11
День 1783. #ЗаметкиНаПолях
Лучшие Практики Логирования с Serilog. Начало

Serilog — это библиотека структурированного логирования для .NET. Она поддерживает множество мест хранения логов, которые называются приёмники данных (sink): от консоли и файлов до управляемых служб ведения журналов, таких как Application Insights. Рассмотрим лучшие практические советы при использовании Serilog.

1. Используйте систему конфигурации
Настроить Serilog в ASP.NET Core можно двумя способами:
1) Fluent API
Позволяет настраивать Serilog в коде. Недостатком является жёстко закодированная конфигурация. Любые изменения требуют развёртывания новой версии.

2) Система конфигурации
Требует установки библиотеки Serilog.Settings.Configuration. После этого добавьте следующий код:
builder.Host.UseSerilog((context, сonfig) =>
сonfig.ReadFrom.Configuration(context.Configuration));

Ниже настраивается запись логов в консоль, а также в блоке Enrich дополнительная информация для записи в лог:
{
"Serilog": {
"Using": ["Serilog.Sinks.Console"],
"MinimiumLevel": {
"Default": "Information",
"Override": {
"Micrsoft": "Information"
}
},
"WriteTo": [
{ "Name": "Console" }
],
"Enrich": [
"FromLogContext",
"WithMachineName",
"WithThreadId"
]
}
}


2. Используйте логирование запросов
Библиотека Serilog.AspNetCore позволяет добавить логирование для конвейера запросов ASP.NET Core. Она добавляет внутренние операции ASP.NET в те же логи, что и события вашего приложения. Для этого надо вызвать метод:
app.UseSerilogRequestLogging();


SourceContext для этого типа логов — Serilog.AspNetCore.RequestLoggingMiddleware. Вот пример вывода:
{
"@t": "2023-12-16T00:00:00.0000000Z",
"@mt": "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms",
"@m": "HTTP POST /api/users responded 409 in 24.7928 ms",
"@i": "37aa1435",
"@r": ["24.7928"],
"@tr": "61a449a8606fdb64e88d6c64b7b7354e",
"@sp": "163ed90674cb12f6",
"ConnectionId": "0HMVSP0L8FVEN",
"CorrelationId": "0HMVSP0L8FVEN:0000000B",
"Elapsed": 24.792778,
"RequestId": "0HMVSP0L8FVEN:0000000B",
"RequestMethod": "POST",
"RequestPath": "/api/users",
"SourceContext": "Serilog.AspNetCore.RequestLoggingMiddleware",
"StatusCode": 409
}


3. Используйте Seq для локальной разработки
Seq — это автономный сервер поиска, анализа и оповещений, созданный для структурированных логов. Его можно бесплатно использовать для локальной разработки. Он предлагает расширенные возможности поиска и фильтрации структурированных логов. Экземпляр Seq можно развернуть в контейнере Docker:
version: '3.4'

services:
seq:
image: datalust/seq:latest
container_name: seq
environment:
- ACCEPT_EULA=Y
ports:
- 5341:5341
- 8081:80

Также в настройках Serilog нужно будет добавить запись логов в Seq. Тогда в панели управления сервера Seq вы сможете анализировать логи.

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

Источник:
https://www.milanjovanovic.tech/blog/5-serilog-best-practices-for-better-structured-logging
👍20
День 1784. #ЗаметкиНаПолях
Лучшие Практики Логирования с Serilog. Окончание

Начало

4. Обогатите логи с помощью CorrelationId
Свойство CorrelationId помогает отслеживать логи, относящиеся к одному и тому же запросу. Это также работает в нескольких приложениях. Для этого необходимо передать CorrelationId через HTTP-заголовок, например, X-Correlation-Id. В примере ниже создадим промежуточное ПО для добавления CorrelationId:

public class RequestLoggingMiddleware
{
private const string HEADER = "X-Correlation-Id";
private RequestDelegate _next;

public RequestLoggingMiddleware(
RequestDelegate next)
{
_next = next;
}

public Task Invoke(HttpContext ctx)
{
var cId = GetCorrelationId(ctx);

using (LogContext.PushProperty(
"CorrelationId", cId))
{
return _next.Invoke(ctx);
}
}

private static string GetCorrelationId(
HttpContext ctx)
{
ctx.Request.Headers.TryGetValue(
HEADER, out StringValues cId);

return cId.FirstOrDefault() ?? ctx.TraceIdentifier;
}
}


Затем добавим промежуточное ПО в конвейер. Обратите внимание, что порядок регистрации важен. Если вам нужен CorrelationId во всех логах, нужно зарегистрировать его в начале:
app.UseMiddleware<RequestContextLoggingMiddleware>();


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

Итого
Все структурированные логи имеют одну и ту же структуру. Это облегчает фильтрацию и поиск конкретной информации в них. Также они предоставляют больше контекста и подробную информацию об ошибках приложений, облегчают выявление и устранение проблем.
Вы можете использовать LogContext в Serilog, чтобы обогатить ваши логи значением CorrelationId, что позволит легко отслеживать все записи, относящиеся к одному запросу.
Когда вы настроите структурированное ведение журнала, вам понадобится искать и анализировать свои журналы. Seq — отличный инструмент для этого, который можно бесплатно использовать для локальной разработки.

Источник: https://www.milanjovanovic.tech/blog/5-serilog-best-practices-for-better-structured-logging
👍15
День 1785. #ЗаметкиНаПолях
Разбираем Native AOT в .NET

Сегодня разберём подробно, что такое AOT-компиляция в .NET.

Ahead-of-Time (AOT) компиляция в .NET представляет собой компиляцию кода C# в нативный код на целевом компьютере.

Традиционная компиляция в .NET включает два этапа:
1) Компиляция C# создаёт файлы DLL, содержащие код на промежуточном языке (IL). Такая DLL называется .NET-сборкой.
2) При выполнении программы среда исполнения .NET (CLR) загружает .NET-сборки. Подсистема CLR (Just-In-Time компилятор) отвечает за компиляцию IL-кода метода при первом его вызове в нативный код, выполняемый непосредственно процессором.

С другой стороны, Native AOT компиляция состоит из одного шага: компиляции исходного кода C# в нативный код машины. Это включает преобразование кода C# в код IL, а затем в нативный код, но это детали реализации.

Преимущества
1) Производительность.
Native AOT значительно сокращает время запуска и повышает общую производительность приложения. Отсутствие накладных расходов на JIT-компиляцию во время выполнения приводит к более быстрому выполнению кода.
2) Упрощённое развертывание.
AOT часто приводит к созданию автономных исполняемых файлов с минимальным или нулевым количеством зависимостей. Это упрощает процессы развёртывания, исключая необходимость установки компонентов среды выполнения.
3) Меньший размер приложения.
Удаляя ненужный код, AOT может значительно уменьшить размер приложения. Это не только экономит место, но и оптимизирует использование памяти, что особенно важно в средах с ограниченными ресурсами, таких как мобильные устройства или устройства IoT.
4) Защита исходного кода.
AOT-компиляция преобразует исходный код в оптимизированный машинный код, который более запутан и сложен для расшифровки, чем IL-код, который можно легко декомпилировать в исходный код C#. Это повышает безопасность чувствительных алгоритмов и бизнес-логики.

Недостатки
1) Компиляция для конкретной платформы
AOT создаёт нативный код для конкретной платформы, адаптированный к конкретной архитектуре или операционной системе. Полученный исполняемый файл не будет работать на другой платформе.
2) Нет поддержки кросс-ОС компиляции.
Например, в Windows нельзя создать версию для Linux и наоборот.
3) Частичная поддержка рефлексии.
Рефлексия опирается на динамическую генерацию кода и обнаружение типов во время выполнения, что противоречит природе предварительной компиляции статического кода при AOT. Хотя до некоторой степени рефлексия всё же работает.
4) Требуются зависимости, совместимые с AOT.
Необходимо, чтобы все зависимости, используемые в проекте, были AOT-совместимыми.
5) Увеличенное время сборки.
Генерация нативного кода может значительно увеличить время сборки, особенно для более крупных проектов или приложений с обширной базой кода.
6) Для разработки требуются инструменты C++ разработки.
AOT может компилироваться только с установленными инструментами для C++ разработки, которые могут занимать до 7Гб на диске.

Поддержка Native AOT .NET 8
Поддерживаются:
- Промежуточное ПО
- Минимальные API
- gRPC
- HTTP-Сервер Kestrel
- Авторизация
- Аутентификация через JWT
- CORS
- Проверки работоспособности
- Кэширование вывода и ответа
- Декомпрессия запроса
- Сжатие ответа
- Статические файлы
- Веб-сокеты
- ADO.NET
- PostgreSQL
- Dapper
- SQLite

Не поддерживаются:
- ASP.NET Core MVC
- WebAPI
- SignalR
- Blazor Server
- Razor Pages
- Сессии
- Spa
- Entity Framework Core

Итого
В .NET 8 Native AOT уже достаточно развит и может использоваться в производстве. Мы, безусловно, можем надеяться, что Microsoft продолжит улучшать поддержку AOT.

Источник: https://blog.ndepend.com/net-native-aot-explained/
👍16
День 1786. #Карьера
12 Качеств Сеньора, Которыми Должны Овладеть Джуны. Начало
Самый быстрый способ достичь вершины в любой области — изучать людей, которые уже находятся на вершине… И делать то, что они делают, а вы — нет. Если вы зарабатываете на жизнь написанием кода, вам стоит обратить внимание на навыки, привычки и отличительные черты старших разработчиков.

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

2. Мышление второго порядка
Отличительная черта джуна – мышление «давайте сделаем это, а там посмотрим». Они склонны сначала писать код, а потом думать. Это может быть хорошим подходом в начале, но, когда вы работаете над промышленным ПО, некоторые технические решения вы не сможете изменить, или это будет слишком сложно.
Старшие разработчики знают, что, если вы влюбитесь в фреймворки и библиотеки, которые знаете лучше всего, вы начнёте принимать неверные технические решения. Сеньоры дважды думают о последствиях своего выбора и о возможных недостатках, которые может иметь их решение.
Учёт обстоятельств второго порядка сделает вас гораздо лучшим разработчиком. Это также поможет вам во время технических собеседований.

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

4. Состоятельность
Из-за неуверенности в себе младшие разработчики постоянно пытаются доказать другим свою состоятельность. То же самое и со старшими разработчиками, которые не уверены в своей ценности и навыках. Они основывают свою уверенность на подтверждении, которое получают от других. Это проигрышная стратегия - пытаться показать, насколько много вы знаете, в технических дискуссиях, обсуждая других или доделывая работу поздно вечером или на выходных. Такое поведение создаёт токсичную командную среду и приводит к неприятным последствиям.
Есть два способа справиться с этим:
1) Прекратить ожидать признания извне. Правда в том, что вам не нужно никому доказывать свою правоту. Только себе.
2) Примириться с негативными голосами в вашей голове. Вы можете быть своим худшим критиком. Перфекционизм, страх неудачи и ошибочное представление о том, кто такой «настоящий» разработчик, разрушают вашу самооценку. Вот почему, как бы усердно вы ни работали, вы всегда будете чувствовать, что терпите неудачу.
Сосредоточьтесь на овладении своим ремеслом, а остальное приложится. Вы мгновенно станете более уверенными в себе и своих силах. И перестанете пытаться доказать свою правоту незнакомцам.

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

Источник:
https://dev.to/dragosnedelcu/12-senior-developer-traits-junior-developers-need-to-master-285m
👍29
День 1787. #Карьера
12 Качеств Сеньора, Которыми Должны Овладеть Джуны. Продолжение

Начало

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

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

7. Ментальные модели
Сеньоры могут понимать более сложные кодовые базы и требования. Они могут понять обстоятельства второго порядка. Как изменения в кодовой базе повлияют на производительность, затраты и даже на команду. И это не потому, что они запоминают каждую деталь реализации, а потому что абстрагируют сложность кода в ментальные модели.
Они это делают, изучая фреймворки и библиотеки и лежащие в их основе паттерны проектирования. Паттернов невероятное множество, поэтому лучший способ начать — это выбрать любую среду, с которой вы работаете, и попытаться выйти за рамки кода.
Начните спрашивать себя, почему создатели фреймворка построили его таким образом? Построен он на новых концепциях или это просто реализация старых принципов (что обычно и происходит)? Так вы построите свои ментальные модели разработки и сможете справляться с более сложными задачами.

8. Коммуникация
Сеньоры могут хорошо вести технические дискуссии и влиять на людей вокруг них: бизнес, менеджеров продукта или коллег-разработчиков, - сеньоры знают, как направить их в правильном направлении. Они делают это, используя сочетание настойчивости, технических знаний и коммуникативных навыков.
Также они могут устанавливать чёткие границы. Не соглашаться, не проявляя эмоций, постоять за себя (и других) во время митингов, агрессивно договориться о своей зарплате.
Если коллега во время проверки кода начнёт обвинять других, сеньор знает, как его остановить. Если руководство пытается вмешиваться в работу и заниматься микроменеджментом, сеньор знает, как прекратить это, не повредив отношениям. Не существует пошагового руководства, как таким стать, поможет только практика. Первый шаг — перестать все время говорить «Да». И начать добиваться своего на переговорах о зарплате.

9. Долгосрочное мышление
Джун думает про «сейчас». Он изменяет фрагмент кода, не задумываясь о его правильном рефакторинге, или просто игнорирует тестирование и производительность. Так же джуны думают о своей карьере: слишком часто меняют работу, сжигая мосты, не совершенствуются, если работа стабильна и т.п.
Хорошие вещи требуют времени. Старшие разработчики знают это и соответствующим образом корректируют своё мышление. Если вы решили, что ваша карьера будет связана с разработкой ПО в ближайшие хотя бы 10 лет, инвестируйте в свои навыки, потому что эти инвестиции окупятся. Инвестируйте в коммуникации, потому что никто не знает, когда то или иное знакомство сможет вам пригодиться.

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

Источник:
https://dev.to/dragosnedelcu/12-senior-developer-traits-junior-developers-need-to-master-285m
👍20
День 1788. #Карьера
12 Качеств Сеньора, Которыми Должны Овладеть Джуны. Окончание

Начало 1-4
Продолжение 5-9

10. Совершенство как привычка
Кажется, Кент Бек произнес знаменитую фразу «Я не великий программист, я просто хороший программист с отличными привычками». Привычки важны, потому что они выдерживают испытание временем. Любые ваши знания о фреймворке рано или поздно устареют. Технологии меняются. Но ваши привычки останутся с вами навсегда.
Люди — существа привычек. Нелегко установить в голове новый распорядок дня. Но как только мы повторим его много раз, это войдёт в привычку, и будет легко доводить дело до конца. Лучший способ воспользоваться этим — поставить любую цель и разбить её на ежедневные действия. Затем превратите эти действия в привычки посредством повторения.
Если вы хотите стать лучше в тестировании, пишите по одному тесту каждый день. Если вы хотите быть более готовым к ежедневным митингам и ясно мыслить, ведите дневник. Найти новую работу и нужно разослать 100 резюме, отправляйте по 10 каждый день.
Выработайте новые привычки, и ваш прогресс будет постоянным и устойчивым.

11. Защита самого важного актива
Сеньор думает на перспективу не только о своём коде и технических решениях, но и о здоровье. Работа разработчиков сидячая, а это очень вредно для здоровья. Кроме того, в офисе есть всякая мусорная еда. Т.е. если вы не приложите сознательных усилий, вам будет трудно оставаться здоровым и в форме.
Но если вы хотите иметь долгую и продуктивную карьеру без проблем со спиной, диабетом или чем-то похуже, нужно начать заботиться о своём здоровье. Это не значит, ходить в спортзал 5 раз в неделю или сесть на сумасшедшую диету. Занимайтесь спортом хотя бы 2–3 раза в неделю, сократите потребление сахара и быстрых углеводов. Подумайте и о психическом здоровье. На живите по принципу «eat, sleep, code, repeat», стройте жизнь вне работы: найдите хобби, встречайтесь с друзьями, отдыхайте.
В краткосрочной перспективе вы можете прогрессировать немного медленнее. Но в долгосрочной перспективе вы сможете оставаться в игре дольше.

12. Замкните круг
Старшие разработчики понимают, как работает карма. Они знают, что как бы усердно они ни работали или насколько бы умны они ни были, десятки людей помогли им на пути к вершине. Это не умаляет их заслуг, но признаёт вклад других людей. Так они замыкают круг - помогают другим разработчикам. И делают это без обязательств. Не потому, что это может принести повышение, уважение или статус, а потому что это правильно.
Вы также можете найти время, чтобы помочь новичку. Помощь тому, кто только начинает, частично вернёт вам вдохновение. Страсть и любопытство, которые испытывает новичок, когда видит что-то впервые, очень заразительны. Ещё одна вещь, которую вы можете превратить в привычку.

Я ничего не упустил? Какие черты характера сеньоров, по-вашему, следует добавить в список? Пишите в комментарии.

Источник: https://dev.to/dragosnedelcu/12-senior-developer-traits-junior-developers-need-to-master-285m
👍21
День 1789. #ЧтоНовенького
Новые Аннотации Данных в .NET 8

В .NET 8 появилось несколько дополнений к аннотациям данных, которые обеспечивают большую гибкость при проверке модели в ASP.NET Core.

1. Указание допустимых значений
Атрибут AllowedValues позволяет добавлять к свойству допустимые значения. Значения представляют собой тип object, что означает, что они могут принимать тип свойства. Т.е. можно использовать строки, числа или даже перечисления. В следующем примере для проверки свойства Fruit в классе ValidatedModel должно быть установлено значение банан или яблоко.
public class ValidatedModel
{
[AllowedValues("banana", "apple")]
public string Fruit { get; set; }
}


2. Запрет определённых значений
Атрибут DeniedValues является противоположностью атрибуту AllowedValues и запрещает определённые значения свойства. Он будет работать с типом, установленным в свойстве. В примере ниже свойство Age не может иметь значение 16 или 17:
public class ValidatedModel
{
[DeniedValues(16, 17)]
public int Age { get; set; }
}


3. Проверка строки в кодировке Base64
Атрибут Base64String гарантирует, что строка должна быть закодирована в Base64. Это гарантирует, что свойство пройдёт проверку только в том случае, если значение закодировано. В примере ниже свойство Hash должно быть закодировано в Base64:
public class ValidatedModel
{
[Base64String]
public string Hash { get; set; }
}


4. Указание длины списка
Если у нас есть свойство типа списка, и мы хотим проверять минимальное и максимальное количество элементов, мы можем использовать атрибут Length. В этом примере длина списка любимых фруктов должна быть от 1 до 3 элементов:
public class ValidatedModel
{
[Length(1, 3)]
public List<string> FavouriteFruits { get; set; }
}


5. Границы диапазонов
Атрибут Range существует уже некоторое время, но в него были добавлены дополнительные необязательные параметры. Если для параметра MinimIsExclusive или MaximumIsExclusive установлено значение true, минимальное или максимальное значение будет исключено из диапазона допустимых значений. В примере ниже задан диапазон от 10 до 19, не включая границы:
public class MyValidationModel
{
[Range(10, 19, MinimumIsExclusive = true, MaximumIsExclusive = true)]
public int Age { get; set; }
}


Источник: https://www.roundthecode.com/dotnet-tutorials/data-annotations-awesome-additions-dotnet-8
👍32
День 1790. #ЗаметкиНаПолях
Перехват HTTP-запросов с помощью DelegatingHandler

Перехват HTTP-запросов позволяет вам выполнить некоторую логику до того, как будет сделан запрос или после получения ответа. В ASP.NET перехватчик можно реализовать с помощью DelegatingHandler.

Вариант использования перехватчика — добавление заголовка (например, авторизации). Если это статическое значение, проще установить его при настройке HttpClient, но, если оно динамическое – это не подойдёт. Другим вариантом использования может быть ведение журнала или обработка ошибок. Нам бы не хотелось засорять процесс выполнения HTTP-запросов слишком большим количеством деталей, а также лучше не повторяться, если одна логика нужна в разных местах.

DelegatingHandler решает эту проблему, предоставляя единую точку, где может существовать эта логика. Общее поведение может быть написано один раз и автоматически применяется ко всем HTTP-запросам (для данного HTTP-клиента). Это упрощает тесты и изменение поведения в будущем.
public class AuthHeaderHandler(
IOptions<AuthOptions> opts) : DelegatingHandler
{
protected override async
Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken ct)
{
var token = CreateToken();
request.Headers.Authorization =
new AuthenticationHeaderValue(
"Bearer", bearerToken);

var response = await
base.SendAsync(request, ct);
return response;
}

private string CreateToken()
{
// Получаем токен
return "…";
}
}

Заметьте, что в примере использован первичный конструктор, добавленный в C# 12.

Чтобы использовать перехватчик, добавьте его в контейнер DI и зарегистрируйте для HTTP-клиента с помощью AddHttpMessageHandler:
var builder = 
WebApplication.CreateBuilder(args);

builder.Services
.AddTransient<AuthHeaderHandler>();

builder.Services
.AddHttpClient<ExternalClient>(
"external-service",
b =>
{
var url = builder.Configuration
.GetSection("ExternalService:Url").Value;
b.BaseAddress = new Uri(url);
})
.AddHttpMessageHandler<AuthHeaderHandler>();

var app = builder.Build();
app.UseHttpsRedirection();
app.Run();

После этих изменений запросы, вызываемые через ExternalClient, будут проходить обработчик AuthHeaderHandler.

Итого
DelegatingHandler позволяет перехватывать HTTP-запрос для выполнения кода до запуска запроса или после получения ответа. Это помогает удалить избыточный код, сохраняя логику приложения чистой и компактной. Подробнее о DelegatingHandler можно почитать в документации.

Источник: https://timdeschryver.dev/blog/intercepting-http-requests-with-a-delegatinghandler
👍12
День 1791. #DDD
Объекты-Значения в .NET. Начало

Объектом-значением (value object) называется объект, который представляет описательный аспект предметной области и не имеет собственной идентичности. Такие объекты создаются в программе для представления элементов проекта, о которых достаточно знать только, что они собой представляют, но не каким именно предметом они являются.
— Эрик Эванс

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

Реализация
Объект Booking ниже с примитивными значениями, представляющими адрес, а также даты начала и окончания бронирования:
public class Booking
{
public string Street { get; init; }
public string City { get; init; }
public string Country { get; init; }
public DateOnly StartDate { get; init; }
public DateOnly EndDate { get; init; }
}

Можно заменить эти примитивы объектами-значениями Address и DateRange:
public class Booking
{
public Address Address { get; init; }
public DateRange Period { get; init; }
}


1. Записи
В С# для представления объектов-значений можно использовать записи. Записи неизменяемы, реализуют структурное равенство и лаконичны за счёт использования первичных конструкторов:
public record Address(
string Street,
string City,
string Country
);

Однако, при этом вы теряете возможность реализовывать инварианты (например, валидацию параметров). Также проблема инвариантов объектов-значений возникает при использовании выражения with.

2. Базовый класс
Альтернативный способ — использование базового класса ValueObject с переопределением равенства (метод GetValues). Наследники должны реализовать этот метод и определить компоненты равенства (равенство каких свойств означает равенство объектов).
Преимущество ValueObject в его явности. Понятно, какие классы домена представляют объекты-значения и какие свойства отвечают за равенство.
public abstract class ValueObject 
: IEquatable<ValueObject>
{
public static bool operator ==
(ValueObject? a, ValueObject? b)
{
if (a is null && b is null)
return true;
if (a is null || b is null)
return false;
return a.Equals(b);
}

public static bool operator !=
(ValueObject? a, ValueObject? b)
=> !(a == b);

public virtual bool Equals
(ValueObject? other)
=> other is not null && ValuesEqual(other);

public override bool Equals
(object? obj)
=> obj is ValueObject v
&& ValuesEqual(v);

public override int GetHashCode() =>
GetValues().Aggregate(
default(int),
(hash, value) =>
HashCode.Combine(hash, value.GetHashCode()));

protected abstract IEnumerable<object> GetValues();

private bool ValuesEqual(ValueObject v) =>
GetValues().SequenceEqual(v.GetValues());
}

Реализация объекта-значения Address:
public sealed class Address : ValueObject
{
public string Street { get; init; }
public string City { get; init; }
public string Country { get; init; }

protected override IEnumerable<object> GetValues()
{
yield return Street;
yield return City;
yield return Country;
}
}


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

Источник:
https://www.milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals
👍16
День 1792. #DDD
Объекты-Значения в .NET. Окончание

Начало

Когда использовать объекты-значения?
Для решения проблемы одержимости примитивами и инкапсуляции инвариантов домена. Инкапсуляция — важный аспект любой модели предметной области. У вас не должно быть возможности создать объект значения в недопустимом состоянии.
Объекты-значения также обеспечивают безопасность типов. Сравните:
public interface IPricingService
{
decimal Calculate(
Apartment apartment,
DateOnly start,
DateOnly end);
}

С реализацией, в которую добавлен объекты-значения:
public interface IPricingService
{
PricingDetails Calculate(
Apartment apartment,
DateRange period);
}

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

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

Сохранение объектов-значений с помощью EF Core
Объекты-значения можно сохранять, используя принадлежащие (Owned) и комплексные (Complex) типы EF Core.

Принадлежащие типы можно настроить, вызвав метод OwnsOne при настройке сущности. Это укажет EF сохранять объекты-значений в той же таблице в виде дополнительных столбцов:
public void Configure(
EntityTypeBuilder<Apartment> builder)
{
builder.ToTable("apartments");
builder.OwnsOne(p => p.Address);
builder.OwnsOne(p => p.Price, b =>
{
b.Property(m => m.Currency)
.HasConversion(
curr => curr.Code,
code => Currency.FromCode(code));
});
}

Замечания:
- Принадлежащие типы имеют скрытое значение ключа.
- Нет поддержки необязательных (обнуляемых) принадлежащих типов.
- Поддерживаются принадлежащие коллекции с помощью OwnsMany.
- Разделение таблиц позволяет сохранять принадлежащие типы отдельно.

Комплексные типы
Это новая функция EF, доступная в .NET 8. Они не идентифицируются и не отслеживаются по значению ключа, поэтому лучше подходят для хранения объектов-значений. Комплексные типы должны быть частью типа сущности:
public void Configure(
EntityTypeBuilder<Apartment> builder)
{
builder.ToTable("apartments");
builder.ComplexProperty(p => p.Address);
}

Ограничения для комплексных типов:
- Нет поддержки коллекций.
- Нет поддержки обнуляемых значений.

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

Источник: https://www.milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals
👍9
День 1793. #ЗаметкиНаПолях
Препарируем Cookie в
ASP.NET Core. Начало
ASP.NET Core создаёт несколько файлов cookie, включая cookie аутентификации, сессии и защиты от подделки (antiforgery). Рассмотрим, что они содержат и как защищены.

Защита cookie
Содержимое этих cookie защищено с помощью комбинации механизмов шифрования и подписи. Эти защитные меры, реализуемые через API защиты данных (DPAPI), обеспечивают конфиденциальность и целостность информации, хранящейся в них.
DPAPI — это платформа для защиты конфиденциальных данных, управления шифрованием и упрощения ротации ключей. Она предлагает унифицированный интерфейс для шифрования, дешифрования и подписи, что повышает безопасность данных приложений.

1. Cookie аутентификации
Поддерживает аутентификацию пользователя по HTTP-запросам. Когда пользователь входит в систему, система аутентификации обычно выдаёт клиенту cookie, вроде следующего:
Set-Cookie: .AspNetCore.cookie=CfDJ8MSO_2XEvwalH…;
expires=Thu, 11 Jan 2024 07:38:16 GMT; path=/; secure; samesite=lax; httponly

DPAPI шифрует его, делая его содержимое недоступным и защищённым от несанкционированного доступа. К счастью, мы можем отключить эту защиту, подставив свой класс защиты данных:
public class MyProtector : IDataProtector
{
public IDataProtector
CreateProtector(string purpose)
{
return new MyProtector();
}

public byte[] Protect(byte[] text)
{
return text;
}

public byte[] Unprotect(byte[] data)
{
return data;
}
}

Добавим этот класс защиты в настройки cookie аутентификации:
builder.Services.AddAuthentication("cookie")
.AddCookie("cookie", o =>
{
o.DataProtectionProvider = new MyProtector();
});

Теперь выдаваемые системой cookie вообще не будут защищены. При входе в систему будет создан cookie аутентификации со всеми утверждениями (claims), которые были заданы в объекте ClaimsPrincipal:
Set-Cookie:
.AspNetCore.cookie=BQAAAAZjb29ra…01U;
expires= Thu, 11 Jan 2024 07:57:04 GMT; path=/; secure; samesite=lax; httponly

Содержимое cookie - просто текст, закодированный в Base64. Если выполнить декодирование (любым инструментом Base64Decode), мы увидим что-нибудь вроде следующего:
......cookie.....cookie...........Jon Smith..........
=https://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.email@server.com...........JobTitle.Developer...........persistent...issued.Thu, 28 Dec 2023 07:57:04 GMT..expires.Thu, 11 Jan 2024 07:57:04 GMT

*здесь нечитаемые символы были заменены точками.

То есть, внутри cookie размещён весь тикет аутентификации (состоящий из ClaimsPrincipal и AuthenticationProperties).

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

Источник:
https://nestenius.se/2023/11/22/exploring-what-is-inside-the-asp-net-core-cookies/
👍16
День 1794. #ЗаметкиНаПолях
Препарируем Cookie в
ASP.NET Core. Окончание
Начало

2. Cookie сессии
Сервис сессий в ASP.NET Core — это механизм управления пользовательскими данными между HTTP-запросами, который часто используется в таких сценариях, как обслуживание корзины покупок. Этот сервис отправляет браузеру защищённый файл cookie для отслеживания данных. Важно знать, что этот cookie и его назначение не связаны с cookie аутентификации.

Приложение может попросить сервис сессий запомнить временные данные текущего пользователя. Чтобы использовать сервис сессий, зарегистрируйте его в конвейере запросов:
// регистрация сервиса
builder.Services.AddSession(options =>
{
//… параметры …
});

//использование сервиса
app.UseSession();


Cookie сессии выглядит примерно так:
Set-Cookie:
.AspNetCore.Session=CfDJ8MSO%2F2XEvalHlF%2Fgv69RLqD6mFWeTVC1MG3dYxNWftq625VyGyqF%2BeIq2
xaqgm1cd4McTp0ydSLcRYraIA4%2F%2Bn89FKFhz567AcC%2FeSnwabg4eRrlFAeWLFBq0K8zl2ISdMPcY0pj%
2BtAJQgC5NIte76QR4TlheM1ZhsD98WAdAvKM; path=/; samesite=lax; httponly

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

Один из вариантов посмотреть на содержимое — загрузить и изменить код промежуточного ПО сессий. Хотя, особой необходимости в этом нет. Cookie сессии хранит простой GUID - ключ, используемый для поиска объекта сессии для текущего запроса. Место хранения данных зависит от того, как вы настроили систему сессий. Система сессий в ASP.NET Core предоставляет различные варианты хранения, включая хранилище в памяти, распределённый кэш и внешних поставщиков хранилищ, что позволяет разработчикам выбирать наиболее подходящий метод для потребностей масштабируемости и производительности приложения.

3. Cookie защиты от подделки
Этот cookie предназначен для защиты от атак с подделкой межсайтовых запросов (CSRF). Он работает в системе на основе токенов, где токен хранится в файле cookie, а также встраивается в HTML-формы. При отправке формы токен из данных формы должен соответствовать токену в файле cookie для обеспечения безопасности.

DPAPI также защищает этот cookie. Реализацией этой защиты занимается класс DefaultAntiforgeryTokenSerializer, подробно изучить который можно здесь.

Вот пример такого cookie:
Set-Cookie: .AspNetCore.Antiforgery.YCD23e8AirM=CfDJ8MSO_2XEvalHlF_gv69RLqDarftldkzWvbxvac
LnZ4WtaTDcgSCPmxQdmK8OHbGfMIUvV0VhcFkx4Ys8jPppklGBUGWRTsXcwxhMBl7nqZA0s_xYN5jJq3J7
LrH8EikY82bOYmSoA_wEYCxkcrBEEAs; path=/; samesite=strict; httponly

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

Источник: https://nestenius.se/2023/11/22/exploring-what-is-inside-the-asp-net-core-cookies/
👍9
День 1795. #ЗаметкиНаПолях
Изоляция Данных в Модульном Монолите
Модульный монолит — это архитектурный подход, который становится очень популярным. Он пытается побороть недостатки монолитной и микросервисной архитектур. Одна из проблем монолитной архитектуры, — тесная связь между компонентами и зависимости между различными частями системы. Модульные монолиты решают эту проблему, чётко определяя границы модулей и шаблоны взаимодействия. Но один аспект, который нельзя упустить из виду, — это изоляция данных между модулями, что гарантирует, независимость и слабую связанность модулей.

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

Модульный монолит имеет строгие правила целостности данных:
- Каждый модуль может обращаться только к своим таблицам.
- Запрещено совместное использование таблиц или объектов между модулями.
- Объединения разрешены только между таблицами одного модуля.

Модули должны быть автономными. Каждый модуль обрабатывает только свои данные. Другие модули могут получить доступ к этим данным, используя общедоступный API модуля.

Уровни изоляции
1. Отдельные таблицы

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

2. Раздельные схемы
Группировка связанных таблиц в БД — это способ обеспечить логическую изоляцию. Вы можете реализовать это, используя схемы. Каждый модуль имеет уникальную схему, содержащую таблицы модуля. Теперь легко отличить, какой модуль какие таблицы содержит. В приложении это легко реализовать, используя несколько контекстов базы данных в EF Core. Вы также можете ввести правила, предотвращающие запрос данных из других модулей и проверять это, например, с помощью тестов архитектуры. Это первый этап изоляции данных при создании модульного монолита.

3. Раздельные БД
Этот подход имеет больше ограничений, чем изоляция данных с помощью схем, однако это лучший вариант, если вам нужны строгие правила изоляции данных между модулями. Недостатком является большая сложность эксплуатации. Вам необходимо управлять инфраструктурой для нескольких БД. Однако это отличный шаг к извлечению модулей. Сначала вы перемещаете таблицы модуля, который хотите извлечь, в отдельную БД. Это также заставит вас решать любые проблемы связанности между вашими модулями. Если все таблицы модуля извлечены в отдельную БД, значит он готов (при необходимости) существовать независимо в виде микросервиса.

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

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

Источник: https://www.milanjovanovic.tech/blog/modular-monolith-data-isolation
👍12
День 1796.
В последний день года предлагаю вам вспомнить его. Поиграем в бинго? Присылайте свои ответы, от чего страдали (или чем наслаждались, кто вас знает 😉) в уходящем году.

PS: В Windows Win+Shift+S для снимка экрана, потом в уведомлениях Snip & Sketch для рисования на нём. Вычёркиваем то, что было.
👍10
День 1797.
Дорогие подписчики, с новым годом!
Желаю вам кода без багов, зелёных тестов и вообще поменьше попадать в ситуации, описанные во вчерашнем «Бинго».
А ещё у меня для вас парочка подарков. Вот такие кружки разыграю среди подписчиков через несколько дней. Всё что нужно сделать, просто оставить комментарий «хочу кружку».
Одно замечание, победители, будьте готовы предоставить ФИО и адрес. И извините, доставка только по РФ.
👍40
День 1798.
Зашёл на подкаст Radio DotNet и подвёл с ними итоги года. Довольно интересно поговорили, не знаю, будет ли интересно слушать, поэтому обязательно послушайте и расскажите, как вам 😊
Отмечу, что со звуком у меня фиговенько, извините, писал на встроенный микрофон.

Слушать тут.
Посмотреть можно тут.
👍17
День 1799. #TipsAndTricks
10 Крутых Трюков в C#. Начало

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

1. Использование атрибутов информации о вызывающем объекте
Эти атрибуты позволяют получить информацию об объекте, вызывающем метод, что может быть полезно для отладки и логирования:
public static void Log(
string message,
[CallerMemberName] string member = "",
[CallerFilePath] string path = "",
[CallerLineNumber] int line = 0)
{
Console.WriteLine(
$"[{member} ({line}) в {path}]: {message}");
}

// вызов
Log("Что-то пошло не так.");

В примере выше метод Log() использует атрибуты информации о вызывающем объекте для автоматического включения имени члена вызывающего объекта, пути к файлу и номера строки в сообщение лога. Это упрощает отслеживание источников в логах без необходимости вручную включать эту информацию.
Пример вывода при вызове из Program.cs:
[<Main>$ (7) в D:\source\Program.cs]: Что-то пошло не так.


2. Эффективные по памяти коллекции с помощью Span<T>
Span<T> — позволяет работать с непрерывными блоками памяти без создания новых массивов или ненужного выделения памяти в куче. Его можно использовать для создания «срезов» существующих массивов или других сегментов памяти без копирования данных.
public static void ProcessArray (byte[] arr)
{
const int size = 1024;
for (int i=0; i<arr.Length; i+=size)
{
int len = Math.Min(size, arr.Length - i);
var chunk = arr.AsSpan(i, len);
ProcessChunk(chunk);
}
}
public static void ProcessChunk(Span<byte> chunk)
{
// Обработка части без создания нового массива
for(int i=0; i<chunk.Length; i++)
chunk[i] *= 2;
}


Как это работает и почему это полезно:
В приведенном выше примере метод ProcessArray обрабатывает большой массив байтов, разделяя его на более мелкие части. Вместо создания новых массивов для каждого фрагмента, что привело бы к увеличению использования памяти и накладным расходам на сбор мусора, метод использует Span<byte> для создания «представления» исходного массива для каждой части. Метод расширения AsSpan используется для создания Span<byte> из исходного массива, начиная с текущего индекса (i) и имеющего желаемую длину (len). Полученный Span<byte> можно использовать как массив, но это не приводит к дополнительному выделению памяти. Метод ProcessChunk демонстрирует, как можно работать со Span<byte> для манипуляций с данными, на которые он указывает. В этом случае значение каждого байта удваивается.
Использование Span<T> может помочь написать более эффективно использующий память и производительный код за счёт уменьшения количества аллокаций в куче и ненужного копирования данных, особенно при работе с большими наборами данных или в сценариях, критичных к производительности.

См. подробнее «Используем Span Для Производительности»

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

Источник:
https://maherz.medium.com/10-mind-blowing-c-hacks-95fa629cfcef
👍33
День 1800. #TipsAndTricks
10 Крутых Трюков в C#. Продолжение

Начало

3. Использование оператора отказа
Оператор отказа (discard) используется для игнорирования значений, которые не нужны в определённом контексте, что делает код более кратким и простым для понимания.
(int min, _) = GetMinMax(numbers);
Console.WriteLine($"Minimum: {min}");

В примере выше отказ (_) используется, чтобы игнорировать значение max, возвращаемое методом GetMinMax(). Это ясно обозначает, что здесь нам не нужно максимальное значение.

Оператор отказа можно использовать в out-параметрах или в сопоставлении по шаблону, когда вам не нужно само значение:
if(int.TryParse("123", out _))
Console.WriteLine("Это целое число.");

if (shape is Circle _)
Console.WriteLine("Фигура - круг");


4. Условная слабая таблица для привязки метаданных
Условная слабая таблица (Conditional Weak Table) позволяет связывать метаданные с объектами без изменения их исходной структуры. Она использует слабые ссылки, поэтому не мешает сборщику мусора утилизировать объекты, когда они больше не используются.
public record Person(string Name);

public static class PersonMetadata
{
private static readonly
ConditionalWeakTable<Person, Dictionary<string, object>>
Metadata = [];

public static void Set(
Person person,
string key,
object value)
{
var metadata = Metadata.GetOrCreateValue(person);
metadata[key] = value;
}

public static object? Get(Person person, string key)
{
if (
Metadata.TryGetValue(person, out var data)
&& data.TryGetValue(key, out var value))
{
return value;
}
return null;
}
}

// использование
var person = new Person("Jon Smith");

PersonMetadata.Set(person, "age", 42);

Console.WriteLine(PersonMetadata.Get(person, "age"));


Как это работает и почему это полезно:
В примере выше запись Person не имеет метаданных. Статический класс PersonMetadata использует ConditionalWeakTable, чтобы связать метаданные с экземплярами Person без изменения исходного типа. Этот подход полезен, когда вы хотите хранить дополнительную информацию для объектов, не изменяя их структуру и не создавая строгие ссылки, которые могут помешать сборке мусора.
Методы Set и Get в классе PersonMetadata позволяют хранить и извлекать метаданные для экземпляров Person. Метаданные хранятся в словаре, который затем связывается с объектом с помощью ConditionalWeakTable. Таблица привязана слабой ссылкой, поэтому, когда объект больше не используется и подлежит сборке мусора, связанные метаданные также будут собраны.

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

Источник:
https://maherz.medium.com/10-mind-blowing-c-hacks-95fa629cfcef
👍36
День 1801. #TipsAndTricks
10 Крутых Трюков в C#. Продолжение

1-2
3-4

5. Конвейеры для потоковой обработки
Pipelines — библиотека потоковой обработки в .NET для эффективной обработки больших потоков данных с низкими задержками. Пространство имён System.IO.Pipelines предоставляет абстракции для чтения и записи данных в потоковом режиме, минимизируя при этом выделение памяти и копирование.
async Task ProcessStream(
Stream stream)
{
var pipe = new Pipe();
var write = FillPipe(stream, pipe.Writer);
var read = ReadPipe(pipe.Reader);
await Task.WhenAll(write, read);
}

async Task FillPipe(
Stream stream,
PipeWriter writer
)
{
const int minSize = 512;
while (true)
{
var memory = writer.GetMemory(minSize);
int bytes = await stream.ReadAsync(memory);
if (bytes == 0)
break;
writer.Advance(bytes);
var result = await writer.FlushAsync();
if (result.IsCompleted)
break;
}
writer.Complete();
}

async Task ReadPipe(
PipeReader reader)
{
while (true)
{
var result = await reader.ReadAsync();
var buffer = result.Buffer;
foreach (var seg in buffer)
{
// Обработка данных частями
Console.WriteLine(
$"Прочитано {seg.Length} байт");
}
reader.AdvanceTo(buffer.End);
if (result.IsCompleted)
break;
}
reader.Complete();
}

// использование
using var stream =
new MemoryStream(
Encoding.UTF8.GetBytes("Hello, Pipelines!"));
await ProcessStream(stream);

Вывод:
Прочитано 17 байт


Как это работает и почему это полезно:
В примере выше метод ProcessStream создаёт новый экземпляр Pipe и запускает две задачи: для записи данных в канал (FillPipe) и для чтения данных из канала (ReadPipe). Task.WhenAll используется для ожидания завершения обеих задач. FillPipe считывает данные из входного потока и записывает их в PipeWriter. Он делает это в цикле, получая память от PipeWriter и считывая данные из потока в память. Метод PipeWriter.FlushAsync вызывается, чтобы сигнализировать о том, что данные доступны для чтения, и цикл продолжается до тех пор, пока поток не будет исчерпан или канал не будет закрыт. Метод ReadPipeAsync ожидает PipeReader.ReadAsync, который возвращает ReadResult, содержащий буфер ReadOnlySequence<byte>. Буфер обрабатывается частями, а метод PipeReader.AdvanceTo вызывается, чтобы сигнализировать о том, что данные были использованы. Цикл продолжается до тех пор, пока конвейер не будет завершён.

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

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

Источник:
https://maherz.medium.com/10-mind-blowing-c-hacks-95fa629cfcef
👍25👎1