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

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

Менеджер: @Spiral_Yuri

РКН: https://clck.ru/3FocB6
Download Telegram
Группировка данных

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
Фанфакт: в C# можно руками создать окно через Win32 API, прям как в C++.

По ощущениям это реально то же самое, что и в C++: регаешь window class, делаешь CreateWindowEx, заводишь WndProc, гоняешь GetMessage/DispatchMessage. Практической пользы почти ноль по сравнению с WinForms, но поиграться прикольно :D

Что в итоге получаешь: очень лёгкую штуку без лишнего оверхеда. Если использовать LibraryImport + NativeAOT (это быстрее и приятнее, чем классический P/Invoke), можно собрать примерно 1MB exe без DLL, который моментально стартует и открывает окно. Делать так в проде обычно незачем, это скорее чисто эксперимент.

И да, туда же спокойно добавляются кнопки и прочие контролы через тот же Win32. Выглядит олдскульно, но зато железобетонно понимаешь, как GUI под капотом работает.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔8👍43👎1🤯1
image_2026-01-30_09-10-14.png
796.9 KB
Шпора по C# для игр

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
😐32🥴5😁2🤯1🤨1
Два файла, которые я создаю еще до того, как напишу первую строку .NET-кода:

Directory.Packages.props и Directory.Build.props

Что это вообще такое?

Это файлы, которые ты добавляешь в solution, чтобы повысить сопровождаемость.

1/ Directory.Build.props

Этот файл централизует общие настройки проектов по всей solution.

версия .NET
nullable reference types
предупреждения компилятора
анализаторы

Вместо того чтобы копировать одну и ту же конфигурацию в каждый csproj, ты задаешь ее один раз.
Все проекты следуют одним правилам, поэтому со временем настройки не начинают “разъезжаться”.

2/ Directory.Packages.props

Этот файл управляет версиями NuGet-пакетов для всей solution.

Ты задаешь версии NuGet в одном месте, и все проекты используют именно их. В больших кодовых базах это убирает целый класс болезненных сюрпризов при сборках и деплоях.

Эти два файла особенно полезны при апгрейде версии .NET:

1. Меняешь в Directory.Build.props версию .NET.
2. Обновляешь версии пакетов в Directory.Packages.props.
3. Всё, готово.

Хочешь, чтобы .NET-решения нормально жили годами, начни с добавления этих двух простых файлов.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
30👍5🍌2
Поток выполнения запроса Entity Framework ⬇️
Please open Telegram to view this post
VIEW IN TELEGRAM
9👌4
Все еще сидишь на .sln в 2026?

Давай покажу, с чем ты реально имеешь дело:

Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApp", "MyApp\MyApp.csproj", "{8A3B4E29-7F7D-4C51-9A18-5D8B9A3E1234}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

.... +200 more unreadable lines?


🤮Везде GUID’ы, вечные merge-конфликты, читать невозможно.

А теперь то же самое в .slnx:

<Solution>
<Project Path="MyApp/MyApp.csproj" />
</Solution>


Вот и всё. Читаемо человеком, дружит с git, никаких GUID’ов.

Почему стоит мигрировать сейчас:

▪️Поддерживается в Visual Studio 2022 (17.10+) и .NET 9+
▪️dotnet sln migrate делает это одной командой
▪️Меньше merge-конфликтов, меньше боли в команде
▪️В code review это наконец можно нормально читать

Как мигрировать:

dotnet sln migrate MyApp.sln


Готово. .slnx создан.

Старый формат делали под внутренности Visual Studio, не под людей. SLNX делают для разработчиков.

Все еще держишься за .sln? Почему?

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍284🔥1
Это markdown-док с практическими рекомендациями по асинхронщине в .NET и типичным граблям, особенно в вебе на ASP.NET Core: читать

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍84
Используй [] вместо Array.Empty в corelib ⬇️
Please open Telegram to view this post
VIEW IN TELEGRAM
13😐9👎1
Знаешь, что даже простая смена C# langversion может повлиять на производительность?

В C# 11 поменялась генерация кода для конверсии method group так, чтобы в некоторых случаях избегать аллокаций.

Пример:

[Benchmark]
public int Lambda()
{
Func<int> func = () => Method();
return func();
}

[Benchmark]
public int MethodGroup()
{
Func<int> func = Method;
return func();
}

static int Method() => 42;


на net4.8 при LangVersion C# 10 и ниже вариант с method group может создавать делегат (то есть аллоцировать) и из-за этого быть заметно медленнее. А при LangVersion C# 11+ компилятор меняет генерацию кода так, чтобы делегат кэшировался, и аллокации пропадают.

В C# 11 поменяли codegen, чтобы кэшировать конверсию method group

C# 10 и ниже (делегат создается на месте):

return new Func<int>((object)null, __methodptr(Method))();


C# 11 и выше (делегат кэшируется):

return (MethodGroupBenchmarks.<>O.<0>__Method ??
(MethodGroupBenchmarks.<>O.<0>__Method = new Func<int>((object)null, __methodptr(Method))))();


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔8