C# Portal | Программирование
14.9K subscribers
912 photos
106 videos
24 files
758 links
Присоединяйтесь к нашему каналу и погрузитесь в мир для C#-разработчика

Связь: @devmangx

РКН: https://clck.ru/3FocB6
Download Telegram
Media is too big
VIEW IN TELEGRAM
Только что вышел Minecraftonia — воксельный движок на C# 13/.NET 9 и Avalonia. В проекте экспериментируют с кастомным воксельным рейтрейсингом, процедурной генерацией террейна и отзывчивым десктопным интерфейсом, при этом всё полностью кроссплатформенно.

Ссылка на релиз: https://github.com/wieslawsoltes/Minecraftonia/releases/tag/v0.1.0

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
😁15🔥14🥴3
В Entity Framework Core ToDictionaryAsync (и синхронный ToDictionary) вытягивают из базы весь объект целиком.

Если глянуть на сигнатуру ToDictionaryAsync:

public static Task<Dictionary<TKey, TElement>> ToDictionaryAsync<TSource, TKey, TElement>(
this IQueryable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
CancellationToken cancellationToken = default)
where TKey : notnull
=> ToDictionaryAsync(source, keySelector, elementSelector, comparer: null, cancellationToken);


Аргументы тут обычные лямбды (Func<TSource, ...>), а не выражения (Expression<Func<...>>). Значит, вычисление этих селекторов происходит на клиенте.

Допустим, у нас есть такой класс:

public class BlogPost
{
public int Id { get; set; }
public required string Title { get; set; }
public required string Description { get; set; }
public required string Content { get; set; }
public required string Author { get; set; }
}


И вот такой запрос:

return await dbContext.BlogPosts
.Where(...)
.ToDictionaryAsync(k => k.Author, v => v.Title);


Звучит красиво, но по факту EF притащит не только Author и Title, а вообще все поля, например, и Content тоже. На это есть issue, и я случайно наткнулся и офигел.

Как обойти проблему: сначала спроецировать данные через Select, это уже выполнится на стороне сервера:

return await dbContext.BlogPosts
.Where(...)
.Select(s => new { Author = s.Author, Title = s.Title })
.ToDictionaryAsync(k => k.Author, v => v.Title);


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
17🔥7🤔2
Мне очень нравится, что в C# 14 убрали лишнюю возню, связанную с полями, лежащими в основе свойств.

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

Ссылка: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14#the-field-keyword

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
15🔥12🥰2👍1
Перемещение Файлов и Папок в Корзину в .NET

При работе с файлами и папками в .NET-приложениях на Windows иногда нужно отправлять объекты в корзину, а не удалять их насовсем (File.Delete, Directory.Delete). Это позволяет пользователю восстановить случайно удалённые данные. Вот как сделать это через Windows Shell API:

static void MoveToRecycleBin(string path)
{
var shellType = Type.GetTypeFromProgID("Shell.Application", throwOnError: true)!;
dynamic shellApp = Activator.CreateInstance(shellType)!;

// https://learn.microsoft.com/en-us/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants?WT.mc_id=DT-MVP-5003978
var recycleBin = shellApp.Namespace(0xa);

// https://learn.microsoft.com/en-us/windows/win32/shell/folder-movehere?WT.mc_id=DT-MVP-5003978
recycleBin.MoveHere(path);
}


Этот метод подходит и для файлов, и для каталогов:

MoveToRecycleBin(@"C:\path\to\file.txt");
MoveToRecycleBin(@"C:\path\to\directory");


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍173🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
От чат‑бота к агентам: Microsoft Agent Framework в .NET

Microsoft опубликовала руководство по переходу с обычных чат-ботов на AI-агентов в .NET. В примерах показывают, как подключить Microsoft Agent Framework к AI-шаблонам, зарегистрировать агента в Program.cs, вынести поиск в инструмент через SearchAsync, настроить Azure OpenAI и векторное хранилище, а также включить телеметрию через OpenTelemetry и тестирование в .NET Aspire.

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

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
6🥴6🤨2👍1
Это может быть первым шагом при написании юнит-тестов.

Не всегда понятно, с чего начать. Но шаблон AAA почти всегда выручает.

// act ... код, который тестируем
// ДОЛЖЕН БЫТЬ ОДИН КОНКРЕТНЫЙ ВЫЗОВ

// arrange ... моки и подготовка окружения для act

// assert ... проверки результата act

И вот — у вас уже есть юнит-тест.

Дальше можно:
a) дублировать Test01 и менять под следующий кейс
b) параметризовать Test01, чтобы прогонять сразу несколько вариантов

В конце просто переименовать Test01..TestN во что-то осмысленное.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15
Requestum — новая быстрая и бесплатная альтернатива MediatR

После новостей о коммерциализации MediatR разработчики начали искать замену. Автор представил Requestum — open source-медиатор под MIT-лицензией, с упором на производительность и простоту миграции.

Библиотека явно разделяет команды, запросы и события, использует отдельные Execute/Handle/Publish, поддерживает sync и async middleware, а также множественных подписчиков. Интеграция с DI минимальная, можно подменить MediatR почти без правок кода.

Бенчмарки показывают ускорение на 20–50% и снижение аллокаций на 30–60%. API прозрачный, CQRS-подход сохраняется, а синхронные обработчики работают реально синхронно.

Requestum уже доступен на NuGet, исходники на GitHub. Хороший вариант для тех, кто не хочет зависеть от лицензий и хочет выжать максимум из .NET-медиатора.

Читать подробнее: https://habr.com/ru/articles/961936/

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
1👨‍💻1
Совет по Visual Studio:

Когда создаешь метод, который возвращает JSON через raw string literal, добавь

/* lang=json*/

Этот момент нигде не задокументирован. Если в JSON будет синтаксическая ошибка вроде пропущенной запятой, Visual Studio подсветит её.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍47🔥157👏2
This media is not supported in your browser
VIEW IN TELEGRAM
Тестируй API прямо в браузере

Есть опенсорсный инструмент Hoppscotch. Позволяет тестировать, дебажить и шарить API прямо из веб-браузера.

Отличная альтернатива Postman. Легкий и быстрый.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
8
Конструкторное внедрение зависимостей и программирование через интерфейсы – основа хорошо тестируемого кода.

Слева: код Боба в PR
Справа: код Дейва в PR

Код Боба проходит ревью
Код Дейва заворачивают

Будь как Боб.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
11💯2
Когда абстракции реально помогают

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

Пример: платежи

Бизнес-логика не должна напрямую зависеть от SDK Stripe. Если захочешь перейти на Adyen или Braintree, не должно быть каскадного переписывания половины проекта. В таком случае абстракция полностью оправдана:

public interface IPaymentProcessor
{
Task ProcessAsync(Order order, CancellationToken ct);
}

public class StripePaymentProcessor : IPaymentProcessor
{
public async Task ProcessAsync(Order order, CancellationToken ct)
{
// Stripe-специфика
}
}

public class AdyenPaymentProcessor : IPaymentProcessor
{
public async Task ProcessAsync(Order order, CancellationToken ct)
{
// Другая API, тот же бизнес-результат
}
}


Теперь доменная логика не завязана на конкретного провайдера:

public class CheckoutService(IPaymentProcessor processor)
{
public Task CheckoutAsync(Order order, CancellationToken ct) =>
processor.ProcessAsync(order, ct);
}


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

Когда абстракции превращаются в техдолг


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

Репозиторий, который потерял смысл

Начиналось всё мило:

public interface IUserRepository
{
Task<IEnumerable<User>> GetAllAsync();
}


Но требования растут:

public interface IUserRepository
{
Task<IEnumerable<User>> GetAllAsync();
Task<User?> GetByEmailAsync(string email);
Task<IEnumerable<User>> GetActiveUsersAsync();
Task<IEnumerable<User>> GetUsersByRoleAsync(string role);
Task<IEnumerable<User>> SearchAsync(string keyword, int page, int pageSize);
Task<IEnumerable<User>> GetUsersWithRecentActivityAsync(DateTime since);
// и дальше понеслась
}


Интерфейс раздувается, запросы лезут прямо в контракты. А ведь EF Core уже умеет делать это из коробки через LINQ: типобезопасно, оптимизируемо и без излишних прокладок. Репозитории были полезны, когда ORM были кривые. Сейчас это часто просто лишнее.

Я тоже таким грешил. Но взросление разработчика — это понимать, когда паттерн становится анти-паттерном.

Сервисные обёртки:

Нормальный пример: обёртка над внешним API

public interface IGitHubClient
{
Task<UserDto?> GetUserAsync(string username);
Task<IReadOnlyList<RepoDto>> GetRepositoriesAsync(string username);
}

public class GitHubClient(HttpClient httpClient) : IGitHubClient
{
public Task<UserDto?> GetUserAsync(string username) =>
httpClient.GetFromJsonAsync<UserDto>($"/users/{username}");

public Task<IReadOnlyList<RepoDto>> GetRepositoriesAsync(string username) =>
httpClient.GetFromJsonAsync<IReadOnlyList<RepoDto>>($"/users/{username}/repos");
}


Все детали HTTP и сериализации спрятаны внутри. Это даёт ценность.

Плохой пример: сервис-прокладка

public class UserService(IUserRepository repo)
{
public Task<User?> GetByIdAsync(Guid id) => repo.GetByIdAsync(id);
public Task<IEnumerable<User>> GetAllAsync() => repo.GetAllAsync();
public Task SaveAsync(User user) => repo.SaveAsync(user);
}


Просто пробрасывание вызовов. Никаких правил, никакой логики. Таких слоёв становится много, а толку то ноль.

Как принимать нормальные решения

- Абстрагируй политику, а не механику
Политика меняется: платёжки, стратегии ретраев, кеши
Механика стабильна: LINQ, HttpClient, JSON

- Жди второй реализации
Одна реализация? Не спеши с интерфейсами.

// сначала просто класс
public class EmailNotifier
{
public Task SendAsync(...) { ... }
}

// потом появляется SMS, и вот тогда...
public interface INotifier
{
Task SendAsync(...);
}


Абстракция должна появляться из реальных требований, а не из фантазий.

- Разграничивай
Внутри приложения — конкретика
На границах системы — абстракции

Как выпиливать неудачные абстракции

Спроси себя: код станет проще без неё? Если да = смело удаляй.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍2
👀👀👀

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
😁17🥴4👏3
В экосистеме ASP.NET Core появилась небольшая, но полезная утилита для управления консольным окном. Разработчик выложил на GitHub пример кода, который позволяет принудительно показать консоль для логирования и менять её заголовок по своему вкусу. Инструмент уже протестирован на Windows 11 и может пригодиться тем, кто пишет сервисы с дополнительным выводом в терминал.

Репозиторий с исходниками:
https://github.com/karenpayneoregon/csharp-basics-2025/blob/master/AspCoreHelperLibrary/WindowHelper.cs

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
1🤔1🥴1
В C# это довольно удобно: var завезли как раз тогда, когда добавили анонимные типы.

Хотя код тут чисто для демонстрации, забавно, что никто не заметил тупой баг на строке 4.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👎31
Rider 2025.3 Release Candidate уже доступен!

В этой сборке собраны все изменения и улучшения, которые будут в следующем крупном релизе. Попробуй и поделись впечатлениями о новых фичах и производительности Rider ;)

Подробнее и ссылка на скачивание тут : https://blog.jetbrains.com/dotnet/2025/11/05/the-rider-2025-3-release-candidate/

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Добавляем Описание в Параметризованные Тесты

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

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

Представим, что у нас есть такой тест:

[Theory]
[MemberData(nameof(InvalidFilters))]
public async Task ShouldNotAllowInvalidInvariants(
LimitFilters filters)
{

}


Тест принимает следующую запись с тестовыми данными:

public record LimitFilters(
Guid? WorkpieceNumber,
IEnumerable<int>? Ids,
IEnumerable<int>? Tools,
IEnumerable<int>? LimitIds);
}


Если мы выполним тест, мы увидим в окне выполнения теста что-то вроде следующего:

ShouldNotAllowInvalidInvariants(filters: { WorkpieceNumber = … })
ShouldNotAllowInvalidInvariants(filters: { WorkpieceNumber = … })


Как видите, сложно понять, о чём каждый тестовый случай, особенно учитывая, что у нас есть коллекции в параметрах. Но обратите внимание, что из-за использования record, мы видим строковое представление записи, т.к. среда выполнения вызывает метод ToString() параметров. Мы можем использовать это.

Чтобы заставить среду выводить более осмысленное описание, мы можем добавить описание теста в LimitDesignerFilters и переопределить метод ToString():

public record LimitDesignerFilters(
string Description,
Guid? WorkpieceNumber,
IEnumerable<int>? Ids,
IEnumerable<int>? Tools,
IEnumerable<int>? LimitIds)
{
public override string ToString()
=> Description;
}


Теперь мы можем задать свойству Description описание каждого тестового случая:

public static TheoryData<LimitDesignerFilters> 
InvalidFilters =>
[
new("Workpiece is null", null, [1], [1], [1]),
new("Param1 is null", Guid.NewGuid(), null, [1], [1]),
];


Тогда в окне выполнения теста мы увидим следующее:

ShouldNotAllowInvalidInvariants(filters: Param1 is null)
ShouldNotAllowInvalidInvariants(filters: Workpiece is null)


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

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
9
В EF Core 10 (preview) появилась поддержка нескольких глобальных фильтров на одну сущность

Раньше в Entity Framework Core можно было задать только один HasQueryFilter для каждой сущности, что было неудобно, если нужно, например, одновременно фильтровать по IsDeleted и TenantId. Теперь в версии 10 это можно делать, но главное, дать каждому фильтру имя.

Пример:

modelBuilder.Entity<Blog>()
.HasQueryFilter("SoftDeletionFilter", b => !b.IsDeleted)
.HasQueryFilter("TenantFilter", b => b.TenantId == tenantId);


Теперь можно управлять каждым фильтром отдельно: включать, отключать или комбинировать как нужно.
Фича уже доступна в preview и обещает сильно упростить жизнь тем, кто работает с multi-tenant и soft delete сценариями.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
6🔥1