C# Portal | Программирование
13.9K subscribers
1.15K photos
126 videos
29 files
930 links
Присоединяйтесь к нашему каналу и погрузитесь в мир для C#-разработчика

Сотрудничество, реклама: @devmangx

Менеджер: @Spiral_Yuri

РКН: https://clck.ru/3FocB6
Download Telegram
Самый быстрый способ перестать ненавидеть регулярки: https://regex101.com/

Собирай, тестируй и дебажь regex пошагово, с объяснениями.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥86👍4
Операторы break и continue с метками в C# 👀

Сейчас это champion proposal


outer: for (int x = 0; x < xMax; x++)
{
for (int y = 0; y < yMax; y++)
{
if (ShouldSkipRest(x, y))
continue outer;

if (ShouldExitAll(x, y))
break outer;
}
}


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯22👏6👍1
Одна строка кода для ускорения EF Core

Когда EF Core загружает связанные сущности через Include(), он генерирует один огромный SQL-запрос с множеством JOIN. Результат? Тысячи дублирующихся строк, гигабайты трафика и медленная работа.

Пример:
var posts = await context.Posts
.Include(p => p.Comments)
.ThenInclude(c => c.Reactions)
.ToListAsync();


При 100 постах, у каждого по 10 комментариев, в каждом по 10 реакций — вместо 11 100 записей получаем результирующий набор на 10 000 строк.

Добавьте .AsSplitQuery() — и EF Core разобьёт один большой запрос на несколько маленьких:
var posts = await context.Posts
.AsSplitQuery() // ← магия здесь
.Include(p => p.Comments)
.ThenInclude(c => c.Reactions)
.ToListAsync();


Query Splitting это не хак и не костыль. Это правильный способ работы с EF Core в read-heavy сценариях.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🤯62🔥1😁1
Публиковать доменные события до или после завершения транзакции?

По сути ты выбираешь между немедленной консистентностью и eventual consistency.

Я всегда публикую доменные события после коммита транзакции, желательно через Outbox.

А ты бы что выбрал и почему?

С EF interceptors можно творить реально интересные штуки.

Публикация доменных событий это всего лишь один из кейсов.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
В .NET 10 JIT умеет убирать аллокацию из-за boxing при итерации по интерфейсам.

Но вот что еще круче: если GetEnumerator реализован через yield return, JIT в некоторых случаях тоже может это оптимизировать!

Классический пример: перебор RepeatedField<T>

public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < this.count; ++i)
yield return this.array[i];
}


Бенчмарк

private readonly RepeatedField<int> _repeatedField 
= GenerateData();

private static RepeatedField<int> GenerateData()
{
var result = new RepeatedField<int>();
result.AddRange(Enumerable.Range(1, 100));
return result;
}

[Benchmark]
public int Foreach_Over_RepeatedField()
{
int result = 0;
foreach (var x:int in _repeatedField)
{
result += x;
}

return result;
}


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
10🔥3
.NET Reverse Proxy в 3 строки кода.

Этого достаточно, чтобы поднять YARP. Понятно, что настройки прокси нужно отдельно задать через appsettings.

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

Пробовал уже YARP?

Балансировка в YARP так же проста: по сути, просто обновляешь proxy settings.

Вот что нужно, чтобы стартануть

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍42🤨1
В .NET 11 Preview 2 вмержили фичу из C# 15: аргументы для collection expressions.

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

List<string> names = [with(capacity: 10), ... elements ...];

Spec: тут

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👎12🤯6👍52
Совет по C#:

if (x == null) { x = new List<string>(); }

x ??= new List<string>();

Результат тот же, но в одну строку.

??= присваивает значение только если слева null.

Доступно начиная с C# 8.0.

Если вы предпочитате первый подход, это не значит что вы плохой разработчик 😜

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
21👍12😁4🥴2
Группировка данных

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

Он строит ILookup<TKey, TElement> это по сути готовая, неизменяемая коллекция групп. В каждой группе лежат элементы с одинаковым ключом.

var users = new[]
{
new { Name = "Anna", Department = "Dev" },
new { Name = "Boris", Department = "Dev" },
new { Name = "Clara", Department = "QA" }
};

var lookup = users.ToLookup(u => u.Department);

foreach (var user in lookup["Dev"])
{
Console.WriteLine(user.Name); // Anna, Boris
}


Достаёшь группу через индексатор lookup["Dev"]. Если такого ключа нет, вернётся пустая последовательность (без исключения).

ToLookup vs GroupBy:

GroupBy возвращает IEnumerable<IGrouping<TKey, TElement>> и работает с отложенным выполнением. То есть группировка реально происходит при переборе, и при повторном переборе может считаться заново.

ToLookup делает всё сразу и держит результат в памяти:

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

Когда ToLookup заходит лучше всего

Используй, если нужно:

➡️много раз обращаться к одним и тем же группам
➡️ не ловить лишние пересчёты группировки
➡️получать пустую выборку вместо исключений при неизвестном ключе
➡️иметь неизменяемую структуру

Не лучший выбор, если:

➡️данных очень много и жалко память
➡️нужна ленивость и потоковая обработка
➡️группировка нужна один раз, тогда проще и дешевле GroupBy

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
6👍6
Забавный трюк для .NET: как в рантайме узнать, в каком файле и на какой строке объявлен метод

Почему не подходят [CallerMemberName]/[CallerFilePath]/[CallerLineNumber]?
Потому что они работают только на месте вызова и требуют менять сигнатуры методов. А если тебе нужно взять локацию *любого* MethodInfo (например, для снапшот-тестов или логов) — это уже мимо.

Идея: Используем Portable PDB: в PDB лежит маппинг IL → исходники (файл, строки, колонки) через sequence points.

Настройка

Обычно portable PDB генерятся по умолчанию, но можно явно:

▪️отдельным файлом:

<PropertyGroup>
<DebugType>portable</DebugType>
</PropertyGroup>


▪️или вшить в сборку:

<PropertyGroup>
<DebugType>embedded</DebugType>
</PropertyGroup>


Реализация (суть)

Читаем PE, ищем embedded pdb, если нет — пытаемся открыть .pdb рядом. Потом по MetadataToken метода достаем MethodDebugInformation, берем первый sequence point и вытаскиваем Document.Name (путь к файлу) + строку/колонку.

Минимальный скелет:

// NuGet: System.Reflection.Metadata (9.*)

public static (string FilePath, SequencePoint SequencePoint)? GetMethodLocation(this MethodInfo methodInfo)
{
var location = methodInfo.DeclaringType?.Assembly.Location;
if (string.IsNullOrEmpty(location)) return null;

using var fs = File.OpenRead(location);
using var reader = new PEReader(fs);

var pdbProvider = reader.ReadDebugDirectory()
.Where(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb)
.Select(e => reader.ReadEmbeddedPortablePdbDebugDirectoryData(e))
.FirstOrDefault();

try
{
if (pdbProvider is null &&
!reader.TryOpenAssociatedPortablePdb(location, File.OpenRead, out pdbProvider, out _))
return null;

var pdbReader = pdbProvider!.GetMetadataReader();

var methodHandle = MetadataTokens.MethodDefinitionHandle(methodInfo.MetadataToken);
var mdi = pdbReader.GetMethodDebugInformation(methodHandle);

if (mdi.SequencePointsBlob.IsNil) return null;

var sp = mdi.GetSequencePoints().FirstOrDefault();
if (sp.Document.IsNil) return null;

var doc = pdbReader.GetDocument(sp.Document);
var filePath = pdbReader.GetString(doc.Name);

return (filePath, sp);
}
finally
{
pdbProvider?.Dispose();
}
}


Пример использования:

var method = typeof(Program).GetMethod(nameof(SampleMethod))!;
var loc = method.GetMethodLocation();

Console.WriteLine($"{loc?.FilePath}:{loc?.SequencePoint.StartLine}");


Важный нюанс: в проде PDB могут быть не задеплоены специально (безопасность/размер/политики), так что всегда держим null-кейсы и не рассчитываем на 100% наличие.

Источник

@KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍61
Расширяющий метод Entity Framework вместо TagWith.

public static class QueryableExtensions
{
public static IQueryable<T> TagWithDebugInfo<T>(
this IQueryable<T> query,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
var debugInfo = $"Caller: {memberName}, File: {filePath}, Line: {lineNumber}";

// также можно добавить другую информацию, которую вы хотите, например, userId и т. д.

return query.TagWith(debugInfo); //delegate to built in TagWith
}
}

//usage sample
var query = context.YourDbSet
.Where(x => x.SomeProperty == someValue)
.TagWithDebugInfo();


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8👏3🔥2
Пожалуйста, пересмотрите решение и всё-таки разрешите асинхронную валидацию моделей 👀

Сейчас это висит в майлстоуне “.NET 11 Planning”, очень хотелось бы, чтобы фичу реально довели до реализации.

@KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
8💯5😐4❤‍🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Проектировать схемы БД становится сильно проще

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

Поддерживает импорт существующих схем, визуальное проектирование с нуля и экспорт результата в разные форматы и даже в код.

https://relatedb.com/

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
18🔥3
OpenTelemetry + .NET: трейсы, метрики и логи в SigNoz

В статье показано развёртывание через docker-compose, генерация быстрых/медленных/ошибочных запросов и просмотр коррелированных трейсов, метрик и логов в SigNoz. Описаны настройки .NET (ResourceBuilder, ActivitySource, авто‑инструментирование, OTLP) и практические советы; исходники в репо.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
Хватит дергать DateTime.UtcNow прямо в бизнес-логике.

Строчка выглядит безобидно, но именно из-за нее тесты потом превращаются в кошмар

Вот типичная ловушка:

* DateTime.UtcNow > user.SubscriptionEnd

Вроде ок, да?

Но как только пытаешься это протестировать, вылезает проблема:

- ты не контролируешь DateTime.UtcNow
- тесты становятся флаки или обрастают костылями
- бизнес-правила оказываются привязаны к инфраструктуре

Есть варианты лучше:

1. Передавать DateTime параметром. Самый простой путь, отлично заходит для маленьких методов.

2. Вынести время в интерфейс IClock. Масштабируется лучше и дружит с тестами.

Когда ты инвертируешь зависимость от времени, доменная логика становится:

проще тестировать
более детерминированной
с нормальным разделением ответственности

В следующий раз, когда пишешь условие, завязанное на время, остановись и спроси:

“Мне нужно будет это тестировать?”

Если да, выноси часы наружу.

@KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🥴9🔥31👍1
Не пиши в SQL = NULL или != NULL

Для NULL всегда используй IS NULL / IS NOT NULL

NULL это “нет значения / неизвестно”, и обычные операторы сравнения (=, !=, <, >) с ним не работают, потому что NULL не равен, не больше и не меньше вообще ничего.

= / != сравнивают значения. А NULL это не значение, а отсутствие значения. Поэтому IS / IS NOT проверяют именно наличие или отсутствие значения.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍23🔥3🥴3
This media is not supported in your browser
VIEW IN TELEGRAM
File Explorer в Visual Studio теперь поддерживает поиск прямо из Solution Explorer.

@KodBlog
👍132🔥2🥴2
Лучший конфиг для HttpClient это тот, который ты не копируешь в каждом сервисе.

В .NET ты настраиваешь все один раз через IHttpClientFactory (и Aspire, если используешь):

* service discovery для вызовов сервис-сервис
* дефолтную resiliency-настройку, чтобы кратковременные фейлы не ломали тебе жизнь

Дальше просто инжектишь клиент и пишешь бизнес-код.

- полный разбор

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👏6
25 Claude Code skills для .NET-разрабов

Покрывает штуки, которые реально важны и неочевидны (типа: как тестировать транзакционные письма в ASP.NET Core?) и вообще помогает улучшать вывод LLM (типы!).

github.com/Aaronontheweb/dotnet-skills?tab=readme-ov-file

Все это про то, чтобы Claude делал вещи нормально без того, чтобы ты на него орал.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
6👍2🥴2
БОЛЬШОЙ SQL грех

Никогда не пихай ORDER BY внутрь CTE (или подзапроса), если там же рядом нет LIMIT.

Если LIMIT забыть, база обычно будет сортировать всю таблицу вообще без смысла.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍43👏2👎1
Как пользоваться LINQ

Чтобы использовать LINQ, нужно подключить System.Linq через using.

➡️ Фильтрация через Where

Если использовать Where, можно коротко вытащить из массива/списка только элементы, которые подходят под условие.
Результат вывода ниже будет 1.

// список int
List<int> idList = new List<int>{1, 2, 3};

// фильтруем: берём только те, где 1
IEnumerable<int> selectList = idList.Where(id => id == 1);


➡️ Цикл через ForEach

Если использовать ForEach, можно так же, как в обычном ForEach, сделать цикл короче, чем for.
Результат вывода ниже будет 1,2,3.

// список int
List<int> idList = new List<int>{1, 2, 3};

// проходимся по всему списку
idList.ForEach(id =>
{
print(id);
}


➡️Комбинации

LINQ при правильной комбинации может сильно сокращать код.
В примере ниже из attackList берутся элементы, где totalAttack != 0, затем они группируются по totalAttack, и группы сортируются по totalAttack.

// группировка по totalAttack (одинаковый totalAttack, разные GridInfo)
IEnumerable<IGrouping<int, GridInfo>> total = attackList
.Where(info => info.totalAttack != 0) // фильтруем где есть totalAttack
.GroupBy(info => info.totalAttack) // группируем по totalAttack
.OrderBy(group => group.Key); // сортируем по totalAttack


➡️Пошаговая обработка через Select

Select позволяет легко обработать элементы массива по порядку.
В коде ниже каждый элемент массива строк по одному конвертируется в int, а затем весь результат превращается в List<int>.
(В других языках это что-то вроде Map: берём элементы массива и преобразуем)

// массив String
string[] idString = {"1", "2", "3"};

// делаем int и превращаем в List
List<int> idList = idString.Select(id => int.Parse(id)).ToList();


➡️Any как OR-условие

Any возвращает true, если хотя бы один элемент удовлетворяет условию.
В коде ниже проверяем, есть ли в списке имён хотя бы одно, которое начинается с "A".

// List строк
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };

// Alice подходит, значит True
names.Any(name => name.StartsWith("A"));


➡️ GetRange: взять диапазон элементов

GetRange позволяет взять из списка элементы по указанному диапазону.
В примере ниже при 0〜5 можно получить "1, 2, 3, 4", но если указать диапазон за пределами размера списка, будет ошибка.

// List int
List<int> idList = new List<int> { 1, 2, 3, 4, 5 };

// берём элементы 0〜4
idList.GetRange(0, 5);

// ошибка 1
idList.GetRange(0, 6);

// ошибка 2
idList.GetRange(1, 5);


➡️Reverse: развернуть список

Reverse разворачивает порядок элементов в списке.
В коде ниже изначально список "1, 2, 3, 4, 5", после Reverse станет "5, 4, 3, 2, 1".

// List int
List<int> idList = new List<int> { 1, 2, 3, 4, 5 };

// выводим 1〜5
idList.ForEach(id =>
{
print(id);
});

// разворот
idList.Reverse();

// выводим 5〜1
idList.ForEach(id =>
{
print(id);
});


➡️Single: получить единственный элемент

Single позволяет получить элемент, который встречается ровно один раз.
В коде ниже пытаемся через Where получить из списка "1, 1, 1, 1, 2" значения 2 и 1.
Для 2 всё ок (он один), а для 1 будет ошибка, потому что 1 встречается несколько раз.

// List int
List<int> idList = new List<int> { 1, 1, 1, 1, 2 };

// берём только 2
int select = idList.Where(id => 2).SingleOrDefault();

// ошибка
int select = idList.Where(id => 1).SingleOrDefault();


➡️Contains: проверить наличие элемента

Contains позволяет проверить, есть ли элемент в списке.
В коде ниже проверяем, есть ли в списке "Alice, Bob, Charlie" значение "Alice".

// List строк
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };

// Alice есть в списке, значит True
names.Contains(name => name.StartsWith("Alice"));


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17🥰3