.NET Разработчик
6.66K subscribers
447 photos
4 videos
14 files
2.15K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2481. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

8. Language Integrated Query (LINQ)
«Расскажите, что такое LINQ и как его можно использовать в приложениях .NET? Приведите примеры различных типов поставщиков LINQ и объясните сценарий, в котором использование LINQ повышает читаемость и эффективность кода».

Хороший ответ
«LINQ, или Language Integrated Query, — это набор технологий, основанных на интеграции возможностей запросов непосредственно в язык C#. LINQ представляет шаблоны для извлечения и обновления данных из различных источников с использованием общего синтаксиса. Он абстрагирует базовый источник данных, позволяя разработчикам использовать один и тот же подход к запросам к БД, XML-документам и коллекциям в памяти.

В .NET существует несколько поставщиков LINQ, каждый из которых предназначен для доступа к различным типам данных:
- LINQ to Objects — используется для запросов к коллекциям в памяти, таким как списки или массивы, реализующие интерфейс IEnumerable.
- LINQ to SQL — предназначен для запросов к базам данных SQL непосредственно из C#.
- LINQ to XML (ранее известный как XLINQ) — предоставляет простые в использовании возможности для запросов и работы с XML-документами.
- LINQ to Entities — часть Entity Framework Core, которая позволяет выполнять запросы к реляционным БД и облачным хранилищам данных через ORM EF Core.

Пример использования LINQ для повышения читабельности кода и его эффективность можно оценить в задачах преобразования данных. Например, представим сценарий, в котором нужно отфильтровать и упорядочить список сотрудников по их зарплате. Без LINQ пришлось бы написать несколько строк императивного кода, включая циклы и условные операторы. С LINQ это можно сделать краткого и ясно в одну строчку:
```csharp
var highSalaryEmployees = employees
.Where(e => e.Salary > 150000)
.OrderBy(e => e.Salary);
```
Это не только сокращает объём кода, но и улучшает его читаемость, чётко выражая его назначение.»


Часто встречающийся неправильный ответ
«LINQ — это способ написания SQL-запросов на C#. Он используется, когда нужно выполнять запросы к базам данных, и работает практически так же, как SQL.»

Этот ответ демонстрирует ограниченное понимание LINQ и упускает из виду его более широкие области применения и преимущества:

- Непонимание области применения LINQ
LINQ предоставляет унифицированную модель для запросов к данным в памяти (например, массивам или спискам), XML и даже к наборам данных из API или других внешних сервисов данных, выходящую далеко за рамки простого взаимодействия с БД.

- Упрощение возможностей
Сравнение с SQL упускает из виду интегрированную природу LINQ в C# и его способность беспрепятственно работать с объектно-ориентированным программированием, предлагая такие функции, как отложенное выполнение и строгая типизация, которые выходят за рамки традиционных возможностей SQL.

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

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍16👎2
День 2482. #SystemDesign101
Современные Сетевые Сервисы
Каждый раз, когда вы открываете браузер, отправляете электронное письмо или подключаетесь к VPN, эти сетевые сервисы незаметно обеспечивают вашу работу по сети.

DNS: Преобразует доменные имена в IP-адреса, чтобы пользователи могли заходить на веб-сайты без необходимости запоминать цифры.

DHCP: Автоматически назначает IP-адреса и сетевые настройки устройствам, подключающимся к сети.

NTP: Синхронизирует часы между системами, обеспечивая единообразие журналов и аутентификации.

SSH: Обеспечивает безопасный удалённый вход в систему и зашифрованную передачу файлов через порт 22.

RDP: Обеспечивает удалённый доступ к рабочему столу систем Windows через порт 3389.

Почта (SMTP-отправка): Обеспечивает безопасную отправку электронных писем от клиентов на почтовые серверы.

HTTPS/HTTP3 (QUIC): Обеспечивает безопасность веб-сайтов, API и обмена данными между приложениями по зашифрованным каналам.

LDAP (по TLS): Выступает в качестве центрального каталога для корпоративных учётных записей и контроля доступа.

OAuth2.0/OpenID Connect: Обеспечивает современные процессы аутентификации, такие как «Войти с помощью Google».

MySQL/PostgreSQL/Oracle: Управляет хранением и извлечением данных на бэкенде для веб- и мобильных приложений.

WireGuard/IPsec: Создаёт зашифрованные туннели для приватного и удалённого доступа к сети.

Источник: https://blog.bytebytego.com
👍7
День 2483. #ЗаметкиНаПолях
Сжатие Запросов HttpClient с Помощью GZIP
Если вам нужно отправлять большие файлы во внешний API, вы можете решить, что необходимо сжимать их перед отправкой. Однако дело в том, что очевидное решение может привести к проблемам с памятью…

Рассмотрим такой код:
internal async ValueTask<HttpRequestMessage>
CreateRequest(Uri uri, Stream source)
{
var outStream = new MemoryStream();
await using (var zip =
new GZipStream(outStream,
CompressionMode.Compress,
leaveOpen: true))
{
await source.CopyToAsync(zip);
zip.Close();
}
outStream.Position = 0;

var request = new HttpRequestMessage(
HttpMethod.Post, uri)
{
Content = new StreamContent(outStream)
};
request.Content.Headers.ContentEncoding.Add("gzip");
return request;
}


Использовать его можно примерно так:
var uri = new Uri("https://my.api/endpoint");
var file = File.OpenRead("largefile.txt");
var request = await CreateRequest(uri, file);
await httpClient.SendAsync(request);

Обычно этот код просто работает. Однако каждый раз при отправке запроса сжатый gzip-контент создаётся в памяти (благодаря MemoryStream) перед отправкой. И если требуется отправлять много таких запросов, приложение начнёт испытывать нехватку памяти.

Решение в gzip-сжатии «на лету»:
public class GzipContent : HttpContent
{
private readonly Stream _source;

public GzipContent(Stream source)
{
_source = source;
Headers.ContentEncoding.Add("gzip");
}

protected override async Task
SerializeToStreamAsync(
Stream stream,
TransportContext? context)
{
await using var zip =
new GZipStream(stream,
CompressionMode.Compress,
leaveOpen: true);
await _source.CopyToAsync(zip);
}

protected override bool
TryComputeLength(out long length)
{
length = -1;
return false;
}
}

Также переделаем CreateRequest:
internal ValueTask<HttpRequestMessage>
CreateRequest(Uri uri, Stream source)
{
return ValueTask.FromResult(
new HttpRequestMessage(HttpMethod.Post, uri)
{
Content = new GzipContent(source)
});
}

Использование будет точно таким же.

Производительность
Вот мои тесты в .NET10. По времени особой разницы нет, зато версия GzipContent выделяет гораздо меньше памяти. Чем больше файл, тем заметнее разница.
| File | Method| Mean   | Ratio| Allocated| Alc Rat |
|-----:|-------|-------:|-----:|---------:|--------:|
| 7KB| Memory| 633.3us| 1.00| 6.35KB| 1.00|
| 7KB| Gzip | 603.5us| 0.96| 4.77KB| 0.75|
| 200KB| Memory| 8.025ms| 1.00| 508.52KB| 1.000|
| 200KB| Gzip | 7.260ms| 0.90| 4.74KB| 0.009|
| 3MB| Memory| 94.97ms| 1.00| 8189.99KB| 1.000|
| 3MB| Gzip | 86.02ms| 0.91| 4.82KB| 0.001|


PS: .NET API может автоматически распаковывать запросы. Достаточно добавить соответствующее промежуточное ПО:
builder.Services.AddRequestDecompression();

// …

app.UseRequestDecompression();


Источник: https://josef.codes/compress-httpclient-requests-with-gzip-dotnet-core/
👍16
День 2484. #ЗаметкиНаПолях
Разбираем Однофайловые Приложения в .NET 10. Начало
C# всегда был немного перегружен церемониями. Даже простейший «Hello World» традиционно требовал файла решения, файла проекта и достаточного количества шаблонного кода, чтобы заставить вас задуматься, а не лучше ли использовать скриптовый язык. В .NET 10 представлены однофайловые приложения.

Что это?
Идея проста: пишете код C# в одном CS-файле и запускаете его напрямую. Не нужно настраивать структуру целого проекта для быстрого служебного скрипта.

Все прелести C# сохраняются: типобезопасность, производительность, богатая стандартная библиотека. Но теперь вы можете использовать его для тех одноразовых скриптов, для которых создание полноценного проекта было бы излишним. По-прежнему можно ссылаться на NuGet-пакеты, на другие проекты C#, использовать конкретные SDK и настраивать свойства проекта — всё это из одного файла с помощью специальных директив.

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

Начало работы
Предположим, нужно быстро проверить, на какой день недели приходится определённая дата. Создадим файл с date-checker.cs:
var targetDate = new DateTime(2025, 12, 31);
Console.WriteLine(
$"Новый год в 2025 - это {targetDate.DayOfWeek}");
Console.WriteLine(
$"Через {(targetDate - DateTime.Today).Days} дней");

Выполним это с помощью:
dotnet run date-checker.cs

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

Пример из реальной жизни
Допустим, нужно быстро обработать JSON и сформировать отчёт. Используем System.Text.Json и CsvHelper:
#:package [email protected]
using System.Text.Json;
using CsvHelper;
using System.Globalization;
using System.Text.Json.Serialization.Metadata;

var json = await
File.ReadAllTextAsync("sales_data.json");
var sales =
JsonSerializer.Deserialize<List<SaleRecord>>(
json,
new JsonSerializerOptions() {
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
});

var topProducts = sales
.GroupBy(s => s.Product)
.Select(g => new {
Product = g.Key,
TotalRevenue = g.Sum(s => s.Amount),
UnitsSold = g.Count()
})
.OrderByDescending(p => p.TotalRevenue)
.Take(10);

using var writer =
new StreamWriter("top_products.csv");
using var csv =
new CsvWriter(writer, CultureInfo.InvariantCulture);
csv.WriteRecords(topProducts);

Console.WriteLine(
"Отчёт сохранён в top_products.csv");

record SaleRecord(string Product, decimal Amount, DateTime Date);

Заметьте, как мы используем и ссылки на NuGet-пакеты, и асинхронные операции, и LINQ, и записи — всё в одном файле, который читается как связный скрипт. Обычно для таких задач используется Python, но теперь можно остаться в C#.

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

Источник:
https://www.milanjovanovic.tech/blog/exploring-csharp-file-based-apps-in-dotnet-10
👍35
День 2485. #ЗаметкиНаПолях
Разбираем Однофайловые Приложения в .NET 10. Окончание

Начало

Создаём что-то более амбициозное с Aspire
Даже AppHost для Aspire можно создать в одном файле:
#:sdk [email protected]
#:package [email protected]

var builder =
DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache")
.WithDataVolume();

var postgres = builder.AddPostgres("postgres")
.WithDataVolume()
.AddDatabase("tododb");

var todoApi =
builder.AddProject<Projects.TodoApi>("api")
.WithReference(cache)
.WithReference(postgres);

builder.AddNpmApp("frontend", "../TodoApp")
.WithReference(todoApi)
.WithReference("api")
.WithHttpEndpoint(env: "PORT")
.WithExternalHttpEndpoints();

builder.Build().Run();


С помощью директивы #:sdk [email protected] ваш отдельный файл становится полноценным оркестратором для распределённого приложения. Вы определяете инфраструктуру, подключаете зависимости и настраиваете полноценную среду разработки — и всё это без создания файла проекта. Это особенно полезно при создании прототипов архитектур или для быстрого развёртывания тестовой среды.

Переход на полноценный проект
Со временем некоторые скрипты перерастают рамки одного файла. Возможно, требуется разделить код на несколько файлов или, может быть, нужна полноценная поддержка IDE для отладки. Переход проходит без проблем:
dotnet project convert MyUtility.cs

Это создаст правильную структуру проекта, сохраняя все ссылки на пакеты и выбранные SDK. Ваш код переместится в Program.cs, и вы получите файл .csproj, отражающий все использованные вами директивы #:.

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

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

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

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

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

Источник: https://www.milanjovanovic.tech/blog/exploring-csharp-file-based-apps-in-dotnet-10
👍9
День 2486. #ЗаметкиНаПолях
Удаляем Старые Версии .NET
Со временем на вашем компьютере и серверах, где вы работаете, может накопиться несколько версий среды выполнения и SDK .NET. Хотя наличие нескольких версий часто необходимо для обеспечения совместимости, старые версии, которые больше не нужны, могут занимать ценное дисковое пространство и загромождать систему. Рассмотрим процесс безопасного определения и удаления старых версий .NET.

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

Проверка установленных версий
Откройте терминал или командную строку и выполните:
dotnet --list-sdks

Это выдаст все установленные версии SDK. Что-то вроде:
5.0.101 [C:\Program Files\dotnet\sdk]
8.0.100-preview.5.23303.2 [C:\Program Files\dotnet\sdk]
9.0.100-preview.6.24328.19 [C:\Program Files\dotnet\sdk]
10.0.100 [C:\Program Files\dotnet\sdk]

Чтобы проверить версии среды выполнения:
dotnet --list-runtimes


Важно перед удалением
Не удаляйте всё! Перед удалением версий учтите:
- Активные проекты: проверьте, на какие версии SDK ориентированы текущие проекты.
- Требования команды: убедитесь, что не удаляете версии, необходимые для командных проектов.
- Конвейеры CI/CD: убедитесь, что конвейеры сборки не зависят от определённых версий.
- Статус поддержки: сохраните хотя бы одну версию, которая в настоящее время поддерживается.
Вы можете проверить жизненный цикл поддержки .NET на официальной странице Microsoft.

Использование инструмента удаления .NET
Microsoft предоставляет официальный инструмент удаления, который делает процесс безопасным и простым. Скачайте msi-файл с официальной страницы релизов GitHub и запустите установку. После завершения установки инструмент dotnet-core-uninstall станет доступен и в командной строке.

Сначала попробуем команду list, чтобы увидеть, какие версии она может найти и удалить:
dotnet-core-uninstall list

Можно использовать команду пробного запуска с фильтром, чтобы увидеть, на что повлияет выполнение удаления:
dotnet-core-uninstall dry-run --all-below 8.0.0 --hosting-bundle

Если вы готовы, удалите версии:
dotnet-core-uninstall remove --all-below 8.0.0 --hosting-bundle

То же можно сделать для сред выполнения и SDK:
dotnet-core-uninstall remove --all-below 8.0.0 --aspnet-runtime
dotnet-core-uninstall remove --all-below 8.0.0 --runtime
dotnet-core-uninstall remove --all-below 8.0.0 --sdk


Полезные опции
--all-below <version>: Удалить все версии ниже указанной;
--all-but-latest: Оставить только последнюю версию;
--all-previews: Удалить все превью-версии;
--sdk: Точная версия SDK;
--runtime: Точная версия среды выполнения;
--force: Пропустить проверку зависимостей (используйте осторожно!).

Рекомендации
Дополнительные советы по управлению версиями .NET Core:
- Использование global.json: прикрепите определённые проекты к определённым версиям SDK.
- Регулярная очистка: запланируйте ежеквартальный просмотр установленных версий.
- Сохраните одну версию LTS: всегда поддерживайте как минимум одну версию с долгосрочной поддержкой.
- Документируйте зависимости: отслеживайте, какие версии нужны проектам.
- Использование Docker: для старых проектов рассмотрите возможность использования контейнеров Docker вместо локальной установки старых SDK.

Итого
Очистка старых версий .NET — это простой процесс, который может освободить значительное количество места на диске и упростить разработку. Официальная утилита .NET Uninstall Tool предоставляет самый безопасный метод с функциями предварительного просмотра, которые позволяют увидеть, какие именно версии будут удалены, прежде чем вносить изменения.

Источник: https://bartwullems.blogspot.com/2025/11/how-to-uninstall-older-net-core-versions.html
1👍26👎1
День 2487. #ВопросыНаСобеседовании #Архитектура
Оптимизируем Генерацию Отчёта

«Пользователь нажимает кнопку в интерфейсе, чтобы создать отчёт в Excel или PDF. Создание отчёта занимает около пяти минут (время может быть произвольным). Пользователю приходится ждать завершения. Как бы вы оптимизировали этот процесс?»

Первая мысль - производительность: как ускорить создание отчётов? Возможно, оптимизировать SQL, сократить количество преобразований данных, кэшировать часть результатов. Если удастся сократить процесс с пяти минут до одной, это будет большой победой.

Но даже это заставит пользователя ждать. Если браузер зависнет, пропадёт соединение, закроется вкладка, - начинай сначала. Проблема не в производительности, а в дизайне.

Мы часто застреваем на мысли «сделать код быстрее». Это не что-то плохое, оптимизация производительности — ценный навык. Но мы не видим более серьёзной проблемы. Приложение выполняет всю работу синхронно, держа пользователя в заложниках до завершения.

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

Однако, не стоит забывать оптимизировать сам код. Запросы к БД, обработка данных и генерация файлов — всё это имеет значение. Возможно, где-то отсутствует индекс, неэффективный цикл или есть более удобная библиотека для создания файлов Excel. Но эти оптимизации — лишь часть решения, а не полная картина.

Как решить проблему долгого ожидания?
Оставим ту же кнопку «Сгенерировать отчёт», но при этом бэкенд примет запрос, сохранит его где-то (например, как запись о задании в БД) и сразу же вернёт управление. В этом суть создания асинхронных API. Затем задание принимается фоновым обработчиком.

В роли обработчика может выступать фоновый сервис, задание Quartz или даже функция AWS Lambda, активируемая сообщением из очереди. Он берёт на себя всю основную работу: извлечение данных, создание файла и его загрузку в хранилище, например, S3 или Azure Blob.

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

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

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

Это интересный вопрос, т.к. он показывает, как люди думают.

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

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

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

Источник: https://www.milanjovanovic.tech/blog/the-interview-question-that-changed-how-i-think-about-system-design
👍22
День 2488. #SystemDesign101
Как Разработать Хороший API
Хорошо спроектированный API кажется интуитивно понятным, он просто работает. Но за этой простотой кроется набор последовательных принципов проектирования, которые делают API предсказуемым, безопасным и масштабируемым.

Вот что отличает хорошие API от плохих.

1. Идемпотентность
GET, HEAD, PUT и DELETE должны быть идемпотентными. Отправка одного и того же запроса несколько раз даёт один и тот же результат и не содержит непреднамеренных побочных эффектов.

POST и PATCH не являются идемпотентными. Каждый вызов создаёт новый ресурс или каким-либо образом изменяет состояние. Используйте ключи идемпотентности, хранящиеся в Redis или в базе данных. Клиент отправляет один и тот же ключ с повторными попытками, сервер распознает его и возвращает исходный ответ, не обрабатывая его повторно.

2. Версионирование
При изменении контракта API меняйте его версию. Указывать версию можно в URL, строке запроса или в заголовках запроса.

3. Имена ресурсов, основанные на существительных
Ресурсы должны быть существительными, а не глаголами. "/api/products", а не "/api/getProducts".

4. Безопасность
Обеспечьте безопасность каждой конечной точки с помощью надлежащей аутентификации. Bearer-токены (например, JWT) включают заголовок, полезную нагрузку и подпись для проверки запросов. Всегда используйте HTTPS и проверяйте токены при каждом вызове.

5. Пагинация
При возврате больших наборов данных используйте параметры пагинации, например "?limit=10&offset=20", чтобы обеспечить эффективность и согласованность ответов.

См. также «REST vs RESTful. В чём Разница?»

Источник: https://blog.bytebytego.com
👍12
День 2489. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

9. Асинхронное Программирование с async и await
«Можете объяснить, как ключевые слова async и await улучшают приложения .NET? Включите в свой ответ краткое объяснение работы этих ключевых слов и приведите пример сценария, в котором асинхронное программирование особенно полезно».

Хороший ответ
Ключевые слова async и await используются в C# для упрощения процесса написания асинхронного кода, что критически важно для выполнения неблокирующих операций. Ключевое слово async определяет метод как асинхронный, указывая, что метод содержит операции, которые могут потребовать ожидания завершения без блокировки вызывающего потока. Ключевое слово await затем используется внутри асинхронного метода для приостановки выполнения метода до завершения ожидаемой задачи, тем самым не блокируя основной поток выполнения.

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

Практический пример того, где async и await полезны, — это приложения с пользовательским интерфейсом, где критически важно поддерживать отзывчивость. Например, сетевой запрос на получение данных из веб-сервиса. Используя async и await, можно вызвать веб-сервис, не замораживая пользовательский интерфейс в ожидании ответа. Это достигается за счёт ожидания сетевой операции, что позволяет потоку пользовательского интерфейса продолжать обработку других событий, таких как пользовательский ввод, до тех пор, пока данные не будут готовы, как показано в следующем коде:
public async Task LoadDataAsync()
{
try
{
// Асинхронно получаем данные
var data = await GetDataAsync();
DisplayData(data);
}
catch (Exception ex)
{
// Обрабатываем ошибки получения данных
ShowError(ex.Message);
}
}

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

Часто встречающийся неудачный ответ:
«async добавляется просто, чтобы ускорить выполнение метода, и await для каждого вызова метода внутри него. Это приводит к одновременному выполнению всех методов.»

В этом ответе неверно истолковываются назначение и функции async и await.

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

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

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

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

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍7👎4
День 2490. #ЧтоНовенького
Увеличиваем Тестовое Покрытие с GitHub Copilot Testing
Написание хороших тестов важно для надёжности ПО, но зачастую отнимает много времени и требует рутинных действий. Теперь GitHub Copilot Testing доступно в виде превью в Visual Studio. Эта новая возможность позволяет создавать модульные тесты на основе ИИ непосредственно в процессе разработки.

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

Ключевые особенности
- Модульные тесты, генерируемые ИИ: автоматическое создание, сборка и запуск тестов для отдельных элементов, файлов, проектов или целых решений.
- Детерминированные, типобезопасные результаты: тесты основаны на семантике компилятора и языка, что обеспечивает согласованность и предсказуемость.
- Поддержка MSTest, xUnit и NUnit: работает с вашими существующими фреймворками и рабочими процессами.
- Интеллектуальная интеграция с Visual Studio: GitHub Copilot Testing интегрировано с Visual Studio, использует анализаторы Roslyn, MSBuild, интеграцию с системами проектов и Test Explorer для обеспечения бесперебойной настройки, выполнения и обнаружения тестов прямо из IDE.
- Автоматическое восстановление тестов: иногда не все тесты проходят сразу — и это нормально. В случае сбоя теста Copilot пытается исправить ошибку, перегенерировать тест и повторно запустить его, чтобы убедиться в его корректной работе

Начало работы
Необходимые условия: последняя сборка Visual Studio 2026 Insiders, код C# и лицензия GitHub Copilot.

Убедитесь, что функция GitHub Copilot Testing (Тестирование GitHub Copilot) включена в разделе Tools > Options > GitHub > Copilot > Testing (Инструменты > Параметры > GitHub > Copilot > Тестирование).

Откройте проект или решение и убедитесь, что сборка проходит без ошибок, чтобы упростить процесс. В окне чата Copilot используйте следующий запрос, чтобы запустить тестирование GitHub Copilot и начать генерацию тестов:
@Test #target

Где #target может быть именем члена, класса, файла, проекта, решения или #changes для git diff.

GitHub Copilot Testing запустит итеративный процесс. Copilot проанализирует ваш код и создаст тестовый проект, если его нет. Затем автоматически cгенерирует тесты, соберёт проект и запустит их.

В обозревателе тестов будут отображаться результаты по мере генерации тестов. После завершения генерации тестов GitHub Copilot Testing предоставит сводку в чате Copilot, которая включает в себя:
- Статистику о количестве созданных или изменённых тестов, файлов и проектов.
- Обзор покрытия до и после, чтобы вы могли легко оценить изменения в покрытии.
- Индикаторы качества отобразят состояние «успех/неудача», а также любые пропущенные или нестабильные случаи для отслеживания.
- Если есть пробелы в тестируемости, они будут перечислены вместе с практическими советами.
- Прямые ссылки на тестовые проекты и файлы.

Источник: https://devblogs.microsoft.com/dotnet/github-copilot-testing-for-dotnet/
День 2491. #ЗаметкиНаПолях
Паттерн Идемпотентный Потребитель. Начало

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

Что может пойти не так при публикации
Предположим, сервис публикует событие при создании новой заметки:
await publisher.PublishAsync(
new NoteCreated(note.Id, note.Title, note.Content));

Конкретная реализация издателя или брокера сообщений не важна.

Теперь представьте:
- Издатель отправляет сообщение брокеру.
- Брокер сохраняет его и отправляет ACK.
- Сбой в сети: ACK не доходит до производителя.
- Производитель по тайм-ауту повторяет попытку публикации.
- Теперь у брокера два события NoteCreated.
С точки зрения производителя, он действовал правильно. Но потребитель получил два события о создании одной и той же заметки.

И это только один из путей возникновения сбоя. Вы также можете получить дубликаты из-за:
- Повторных доставок брокером.
- Сбоев потребителя + повторных попыток.
Т.е. даже если вы всё сделали «правильно» на издателе, потребителю всё равно придётся защищаться.

Идемпотентность на стороне издателя (управляет брокер)
Многие брокеры сообщений поддерживают идемпотентную публикацию посредством дедупликации сообщений, если вы укажете уникальный идентификатор сообщения. Например, Azure Service Bus может обнаруживать дубликаты и игнорировать повторные публикации сообщений с тем же идентификатором в течение заданного периода. Amazon SQS и другие брокеры также предлагают аналогичные гарантии.

Не нужно заново изобретать эту логику в вашем приложении. Ключ к успеху — назначить каждому сообщению стабильный идентификатор, уникально отражающий отправляемое логическое событие. Например, при публикации события NoteCreated:
var message = new NoteCreated(
note.Id, note.Title, note.Content)
{
MessageId = Guid.NewGuid() // либо note.Id
};

await publisher.PublishAsync(message);

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

Эта идемпотентность на уровне брокера решает широкий класс проблем на стороне производителя: повторные попытки сети, временные сбои и дублирующиеся публикации.

Она не обрабатывает повторные попытки потребителя, которые происходят при повторной доставке сообщений или сбое вашего сервиса во время обработки.

С этим поможет шаблон «Идемпотентный потребитель».

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

Источник:
https://www.milanjovanovic.tech/blog/the-idempotent-consumer-pattern-in-dotnet-and-why-you-need-it
👍11