Media is too big
VIEW IN TELEGRAM
Только что вышел Minecraftonia — воксельный движок на C# 13/.NET 9 и Avalonia. В проекте экспериментируют с кастомным воксельным рейтрейсингом, процедурной генерацией террейна и отзывчивым десктопным интерфейсом, при этом всё полностью кроссплатформенно.
Ссылка на релиз: https://github.com/wieslawsoltes/Minecraftonia/releases/tag/v0.1.0
👉 @KodBlog
Ссылка на релиз: https://github.com/wieslawsoltes/Minecraftonia/releases/tag/v0.1.0
Please open Telegram to view this post
VIEW IN TELEGRAM
😁15🔥14🥴3
В Entity Framework Core ToDictionaryAsync (и синхронный ToDictionary) вытягивают из базы весь объект целиком.
Если глянуть на сигнатуру ToDictionaryAsync:
Аргументы тут обычные лямбды (Func<TSource, ...>), а не выражения (Expression<Func<...>>). Значит, вычисление этих селекторов происходит на клиенте.
Допустим, у нас есть такой класс:
И вот такой запрос:
Звучит красиво, но по факту EF притащит не только Author и Title, а вообще все поля, например, и Content тоже. На это есть issue, и я случайно наткнулся и офигел.
Как обойти проблему: сначала спроецировать данные через Select, это уже выполнится на стороне сервера:
👉 @KodBlog
Если глянуть на сигнатуру 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);
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
Особенно круто для таких штук, как INotifyPropertyChanged, потому что больше не нужен отдельный приватный field только ради вызова уведомления.
Ссылка: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14#the-field-keyword
Please open Telegram to view this post
VIEW IN TELEGRAM
❤15🔥12🥰2👍1
Перемещение Файлов и Папок в Корзину в .NET
При работе с файлами и папками в .NET-приложениях на Windows иногда нужно отправлять объекты в корзину, а не удалять их насовсем (File.Delete, Directory.Delete). Это позволяет пользователю восстановить случайно удалённые данные. Вот как сделать это через Windows Shell API:
Этот метод подходит и для файлов, и для каталогов:
👉 @KodBlog
При работе с файлами и папками в .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");
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17❤3🔥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, вынести поиск в инструмент через
Есть упоминание расширений, мультиягентов, кастомного middleware и развёртывания в Azure с рабочими примерами. Агент уже не болтает, а самостоятельно решает задачи.
👉 @KodBlog
Microsoft опубликовала руководство по переходу с обычных чат-ботов на AI-агентов в .NET. В примерах показывают, как подключить Microsoft Agent Framework к AI-шаблонам, зарегистрировать агента в Program.cs, вынести поиск в инструмент через
SearchAsync, настроить Azure OpenAI и векторное хранилище, а также включить телеметрию через OpenTelemetry и тестирование в .NET Aspire. Есть упоминание расширений, мультиягентов, кастомного middleware и развёртывания в Azure с рабочими примерами. Агент уже не болтает, а самостоятельно решает задачи.
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
Не всегда понятно, с чего начать. Но шаблон AAA почти всегда выручает.
// act ... код, который тестируем
// ДОЛЖЕН БЫТЬ ОДИН КОНКРЕТНЫЙ ВЫЗОВ
// arrange ... моки и подготовка окружения для act
// assert ... проверки результата act
И вот — у вас уже есть юнит-тест.
Дальше можно:
a) дублировать Test01 и менять под следующий кейс
b) параметризовать Test01, чтобы прогонять сразу несколько вариантов
В конце просто переименовать Test01..TestN во что-то осмысленное.
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
После новостей о коммерциализации 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/
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1👨💻1
Совет по Visual Studio:
Когда создаешь метод, который возвращает JSON через raw string literal, добавь
/* lang=json*/
Этот момент нигде не задокументирован. Если в JSON будет синтаксическая ошибка вроде пропущенной запятой, Visual Studio подсветит её.
👉 @KodBlog
Когда создаешь метод, который возвращает JSON через raw string literal, добавь
/* lang=json*/
Этот момент нигде не задокументирован. Если в JSON будет синтаксическая ошибка вроде пропущенной запятой, Visual Studio подсветит её.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍47🔥15❤7👏2
This media is not supported in your browser
VIEW IN TELEGRAM
Тестируй API прямо в браузере
Есть опенсорсный инструмент Hoppscotch. Позволяет тестировать, дебажить и шарить API прямо из веб-браузера.
Отличная альтернатива Postman. Легкий и быстрый.
👉 @KodBlog
Есть опенсорсный инструмент Hoppscotch. Позволяет тестировать, дебажить и шарить API прямо из веб-браузера.
Отличная альтернатива Postman. Легкий и быстрый.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8
Конструкторное внедрение зависимостей и программирование через интерфейсы – основа хорошо тестируемого кода.
Слева: код Боба в PR
Справа: код Дейва в PR
Код Боба проходит ревью
Код Дейва заворачивают
Будь как Боб.
👉 @KodBlog
Слева: код Боба в PR
Справа: код Дейва в PR
Код Боба проходит ревью
Код Дейва заворачивают
Будь как Боб.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11💯2
Критические изменения в Entity Framework 10
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes
👉 @KodBlog
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes
Please open Telegram to view this post
VIEW IN TELEGRAM
Docs
Breaking changes in EF Core 10 (EF10) - EF Core
List of breaking changes introduced in Entity Framework Core 10 (EF10)
🤯1
Когда абстракции реально помогают
Хорошие абстракции изолируют то, что действительно меняется. То, что завтра может поехать.
Пример: платежи
Бизнес-логика не должна напрямую зависеть от SDK Stripe. Если захочешь перейти на Adyen или Braintree, не должно быть каскадного переписывания половины проекта. В таком случае абстракция полностью оправдана:
Теперь доменная логика не завязана на конкретного провайдера:
Абстракция закрывает нестабильную зависимость . Меняется только один класс, когда провайдеры выкатывают новые API или ты решаешь их сменить. Это хороший пример: он реально приносит пользу.
Когда абстракции превращаются в техдолг
Проблема начинается, когда абстрагируем то, что вообще-то стабильно. Получается лишний слой, который никому не нужен.
Репозиторий, который потерял смысл
Начиналось всё мило:
Но требования растут:
Интерфейс раздувается, запросы лезут прямо в контракты. А ведь EF Core уже умеет делать это из коробки через LINQ: типобезопасно, оптимизируемо и без излишних прокладок. Репозитории были полезны, когда ORM были кривые. Сейчас это часто просто лишнее.
Я тоже таким грешил. Но взросление разработчика — это понимать, когда паттерн становится анти-паттерном.
Сервисные обёртки:
Нормальный пример: обёртка над внешним API
Все детали HTTP и сериализации спрятаны внутри. Это даёт ценность.
Плохой пример: сервис-прокладка
Просто пробрасывание вызовов. Никаких правил, никакой логики. Таких слоёв становится много, а толку то ноль.
Как принимать нормальные решения
- Абстрагируй политику, а не механику
Политика меняется: платёжки, стратегии ретраев, кеши
Механика стабильна: LINQ, HttpClient, JSON
- Жди второй реализации
Одна реализация? Не спеши с интерфейсами.
Абстракция должна появляться из реальных требований, а не из фантазий.
- Разграничивай
Внутри приложения — конкретика
На границах системы — абстракции
Как выпиливать неудачные абстракции
Спроси себя: код станет проще без неё? Если да = смело удаляй.
👉 @KodBlog
Хорошие абстракции изолируют то, что действительно меняется. То, что завтра может поехать.
Пример: платежи
Бизнес-логика не должна напрямую зависеть от 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(...);
}
Абстракция должна появляться из реальных требований, а не из фантазий.
- Разграничивай
Внутри приложения — конкретика
На границах системы — абстракции
Как выпиливать неудачные абстракции
Спроси себя: код станет проще без неё? Если да = смело удаляй.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍2
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
😁17🥴4👏3
В экосистеме
Репозиторий с исходниками:
https://github.com/karenpayneoregon/csharp-basics-2025/blob/master/AspCoreHelperLibrary/WindowHelper.cs
👉 @KodBlog
ASP.NET Core появилась небольшая, но полезная утилита для управления консольным окном. Разработчик выложил на GitHub пример кода, который позволяет принудительно показать консоль для логирования и менять её заголовок по своему вкусу. Инструмент уже протестирован на Windows 11 и может пригодиться тем, кто пишет сервисы с дополнительным выводом в терминал.Репозиторий с исходниками:
https://github.com/karenpayneoregon/csharp-basics-2025/blob/master/AspCoreHelperLibrary/WindowHelper.cs
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1🤔1🥴1
В C# это довольно удобно: var завезли как раз тогда, когда добавили анонимные типы.
Хотя код тут чисто для демонстрации, забавно, что никто не заметил тупой баг на строке 4.
👉 @KodBlog
Хотя код тут чисто для демонстрации, забавно, что никто не заметил тупой баг на строке 4.
Please open Telegram to view this post
VIEW IN TELEGRAM
👎3❤1
Rider 2025.3 Release Candidate уже доступен!
В этой сборке собраны все изменения и улучшения, которые будут в следующем крупном релизе. Попробуй и поделись впечатлениями о новых фичах и производительности Rider ;)
Подробнее и ссылка на скачивание тут : https://blog.jetbrains.com/dotnet/2025/11/05/the-rider-2025-3-release-candidate/
👉 @KodBlog
В этой сборке собраны все изменения и улучшения, которые будут в следующем крупном релизе. Попробуй и поделись впечатлениями о новых фичах и производительности Rider ;)
Подробнее и ссылка на скачивание тут : https://blog.jetbrains.com/dotnet/2025/11/05/the-rider-2025-3-release-candidate/
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Добавляем Описание в Параметризованные Тесты
Часто нам требуется протестировать несколько вариантов использования метода с разными данными, и для этого подойдут параметризованные тесты, например, Theory в xUnit.
При этом бывает полезно добавить не только тестовые данные, но и описание к каждому тестовому случаю. Рассмотрим, как это сделать.
Представим, что у нас есть такой тест:
Тест принимает следующую запись с тестовыми данными:
Если мы выполним тест, мы увидим в окне выполнения теста что-то вроде следующего:
Как видите, сложно понять, о чём каждый тестовый случай, особенно учитывая, что у нас есть коллекции в параметрах. Но обратите внимание, что из-за использования record, мы видим строковое представление записи, т.к. среда выполнения вызывает метод ToString() параметров. Мы можем использовать это.
Чтобы заставить среду выводить более осмысленное описание, мы можем добавить описание теста в LimitDesignerFilters и переопределить метод ToString():
Теперь мы можем задать свойству Description описание каждого тестового случая:
Тогда в окне выполнения теста мы увидим следующее:
Тут всё ещё присутствует название параметра (filters), но всё же, понять, что проверяет каждый тест, уже гораздо проще.
👉 @KodBlog
Часто нам требуется протестировать несколько вариантов использования метода с разными данными, и для этого подойдут параметризованные тесты, например, 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), но всё же, понять, что проверяет каждый тест, уже гораздо проще.
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 это можно делать, но главное, дать каждому фильтру имя.
Пример:
Теперь можно управлять каждым фильтром отдельно: включать, отключать или комбинировать как нужно.
Фича уже доступна в preview и обещает сильно упростить жизнь тем, кто работает с multi-tenant и soft delete сценариями.
👉 @KodBlog
Раньше в 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 сценариями.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6🔥1