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

Связь: @devmangx

РКН: https://clck.ru/3FocB6
Download Telegram
Получение информации о .NET Runtime в приложении

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
❤‍🔥9👍43
Этот пост вряд ли принесёт вам практическую пользу в повседневной жизни, ну, разве что улыбку. Давайте доведём ValueTuple до крайности ⌨️

Тип ValueTuple это удобный синтаксический сахар. Например, если у вас есть такой код, даже если вы назвали элементы кортежа:

(var one, var two) = MyFunc();
Console.WriteLine(one + two);

(int ANumber, int AnotherNumber)
MyFunc() => (1,2);


Компилятор сгенерирует следующее:

[CompilerGenerated]
internal class Program
{
private static void <Main>$(string[] args)
{
ValueTuple<int, int> valueTuple = <<Main>$>g__MyFunc|0_0();
int item = valueTuple.Item1;
int item2 = valueTuple.Item2;
Console.WriteLine(item + item2);
}

[CompilerGenerated]
[return: TupleElementNames(new string[] { "ANumber", "AnotherNumber" })]
internal static ValueTuple<int, int> <<Main>$>g__MyFunc|0_0()
{
return new ValueTuple<int, int>(1, 2);
}
}


То есть компилятор создаёт список Item1, Item2 и так далее для всех элементов кортежа. Но интересный момент возникает, если элементов больше 7:

var (a,b,c,d,e,f,g,h) = MyFunc();
Console.WriteLine(a + b + c + d + e + f + g + h);

(int a, int b, int c, int d, int e, int f, int g, int h) MyFunc()
=> (1,2,3,4,5,6,7,8);


Компилятор «понизит» это до:

ValueTuple<int, int, int, int, int, int, int, ValueTuple<int>> valueTuple = <<Main>$>g__MyFunc|0_0();
int item = valueTuple.Item1;
int item2 = valueTuple.Item2;
int item3 = valueTuple.Item3;
int item4 = valueTuple.Item4;
int item5 = valueTuple.Item5;
int item6 = valueTuple.Item6;
int item7 = valueTuple.Item7;
int item8 = valueTuple.Rest.Item1;
Console.WriteLine(item + item2 + item3 + item4 + item5 + item6 + item7 + item8);


После седьмого элемента компилятор создаёт новое свойство Rest, которое содержит «остальные» элементы. Если переборщить, например так:

(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j,  int k, int l, int m, int n, int o, int p, int q, int r, int s, int t, int u, int v, int w, int x, int y, int z, int aa, int bb, int cc, int dd, int ee, int ff, int gg, int hh, int ii, int jj, int kk, int ll, int mm, int nn, int oo, int pp, int qq, int rr, int ss, int tt, int uu, int vv, int ww, int xx) MyFunc()
{
return (1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50);
}


И использовать так:

var (a,b,c,....xx) = MyFunc();


То получится что-то вроде valueTuple.Rest.Rest.Rest.Rest.Item1

Если хотите поиграться с этим: sharplab.io

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯114👍4😁1
Rider 2025.2.3 был выпущен

Узнать об изменениях в этой сборке и скачать обновление можно здесь:

https://blog.jetbrains.com/dotnet/2025/10/06/resharper-and-rider-2025-2-3/

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍72
Соглашения об именовании в C#

Согласны или нет?

Константы — в SCREAMING_CASE с подчёркиваниями

Поля экземпляра — в camelCase с префиксом подчёркивания

Статические поля — в PascalCase

Свойства — в PascalCase

Локальные переменные — в camelCase

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
💯21🤯84👍3
Улучшаем отладку EF Core с помощью тегов запросов

Отслеживание SQL-запросов, которые генерирует EF Core, порой похоже на поиск иголки в стоге сена. Когда ваше приложение создаёт десятки или сотни запросов, понять, какое LINQ-выражение породило конкретный SQL, становится настоящей головной болью. К счастью, EF Core предлагает удобный инструмент —> теги запросов.

Что такое теги запросов 😜

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

Чтобы добавить тег, используется метод TagWith для любого IQueryable:

var orders = context.Orders
.TagWith("Заказы больше $1000")
.Where(o => o.Total > 1000)
.Include(o => o.Customer)
.ToList();


Сгенерированный SQL будет выглядеть примерно так:

-- Заказы больше $1000
SELECT [o].[Id], [o].[CustomerId], [o].[Total], [c].[Id], [c].[Name]
FROM [Orders] AS [o]
INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]
WHERE [o].[Total] > 1000.0


Благодаря тегу сразу видно, какой код породил запрос, без необходимости вручную сопоставлять SQL с LINQ.

Дополнительные возможности

Можно добавлять несколько тегов для большего контекста, а также включать значения переменных во время выполнения:

var userOrders = context.Orders
.TagWith("Запрос с дэшборда")
.TagWith($"User ID: {userId}")
.TagWith($"CorrelationId: {correlationId}")
.Where(o => o.CustomerId == userId)
.OrderByDescending(o => o.OrderDate)
.Take(10)
.ToList();


Результат:

-- Запрос с дэшборда
-- User ID: 12345
-- CorrelationId: 987654321
SELECT TOP(10) [o].[Id], [o].[CustomerId], [o].[OrderDate]
FROM [Orders] AS [o]
WHERE [o].[CustomerId] = 12345
ORDER BY [o].[OrderDate] DESC


*CorrelationId помогает проследить весь путь пользовательского запроса

Важные замечания

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

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

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍215🔥2
Два почти одинаковых SQL-запроса.

Один оказался быстрее в 451 раз.

Реализовывалась курсорная пагинация, и, казалось бы, логично —> добавить индекс, чтобы ускорить запрос.

Но именно здесь всё пошло не так.

Появился Index Scan по составному индексу и вроде бы отлично. Однако запрос стал медленнее, чем без индекса.

Почему так произошло?

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

Решение — использовать tuple comparison в SQL.

После этого индекс наконец заработал: 0.668 ms.

Оказалось, что оптимизатор запросов не всегда понимает, что составной индекс можно применить для построчного сравнения.

При tuple comparison индекс начинает использоваться корректно.

Без анализа плана выполнения запроса это было бы сложно заметить.

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

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
10🔥5👏3👍1
Проверка на null, которая на самом деле null не проверяет 🚨

Рассмотрим такой код:

string Test1(List<string> strs)
{
if(strs is [var s])
{
if (s.Length > 0)
return s;
}
return string.Join(",", strs);
}


Мы сопоставляем массив строк с шаблоном (массив из одного элемента) и, если элемент не пустая строка, возвращаем его. Выглядит красиво и лаконично, правда?

Но на практике такой код может внезапно выдать NullReferenceException

На SharpLab можно посмотреть три варианта такой проверки и понять, что происходит «под капотом»:

// if (text.Length > 0)
// нет проверки на null!!!
if(strs is [var s])

// if (text != null && text.Length > 0)
if(strs is [string s])

// if (text != null && text.Length > 0)
if(strs is [{} s])


Оказывается, есть различие между шаблоном var (допускает null) и шаблоном не-var. Третий вариант — это шаблон не-null, который делает то же самое (но не требует явного указания типа). Обычно var используется вместо явного типа лишь для удобства чтения, но здесь у нас есть реальная разница в поведении.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🌚6🍌2🌭1🍓1
Однофайловые скрипты в .NET

Dev Containers для изоляции и воспроизводимости окружения
.NET 10 Preview 7
Entity Framework Core

Dev Containers — это действительно удобно, особенно учитывая, сколько всего они могут настроить автоматически (например, SQL Server).
VS Code делает процесс настройки максимально простым.

Однофайловые скрипты поддерживаются только в VS Code и .NET CLI.

Подключение NuGet-пакетов:

#:package <имя_пакета>@<версия>


Настройка свойств MSBuild:

#:property <имя_свойства>=<значение>


Слева — C#-код однофайлового скрипта,
в центре — Dockerfile (из Dev Containers),
справа — конфигурация .devcontainer

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
6👍4🥴1
Псевдопеременные в отладчике Visual Studio для C#

В Visual Studio есть специальные псевдопеременные, которые показывают полезную информацию в окне Watch или Immediate. Их можно вводить как обычные переменные, но они не связаны с именами переменных в коде.

Основные псевдопеременные:

1. $exception

Оценивает текущее выброшенное исключение. Переменная доступна в момент выбрасывания исключения, а также в блоке catch, если исключение перехвачено. Это полезно в случаях, когда вы не добавили параметр Exception в предложение catch. Вы также можете увидеть исключение в окне Locals.

2. $returnvalue

Показывает и возвращает значение метода, из которого только что вышли. Это полезно, если вы не присваиваете возвращаемое значение какой-то переменной (а, например, сразу используете return). Заметьте, однако, что значение будет доступно только сразу после выхода из метода:

- на следующей строке, если вы поставили там точку останова, либо перескочили (F10) через вызов метода;
- при выходе из метода (на строке с закрывающей скобкой после return), если вы проходите по коду самого метода.
Кроме того, возвращаемое значение автоматически показывается в окнах Locals и Autos.

3. $user

Показывает информацию о текущем пользователе ОС и процессе. Сюда входит имя машины, привилегии процесса и некоторые другие сведения, связанные с безопасностью.

4. $threadSmallObjectHeapBytes

Отображает общее количество байт, выделенных в куче малых объектов (Small Object Heap) текущим потоком. (.NET 6+)

5. $threadUserOldHeapBytes

Отображает общее количество байт, выделенных в Пользовательской Куче Старшего поколения (User Old Heap) текущим потоком. User Old Heap = Large Object Heap + Pinned Object Heap (.NET 6+)

6. $1, $2, …, $n

Отображают по порядку объекты, для которых создан Object ID при отладке.

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

Источник: тык

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍83
В .NET 10 появился DATAS — механизм, который динамически подстраивает размер кучи под реальную нагрузку. Для Server GC это может заметно сократить потребление памяти, но в некоторых случаях снизить пропускную способность, поэтому стоит проверить метрики и при необходимости подстроить или отключить DATAS.

Механизм вычисляет Gen0-бюджет по Live Data Size (BCD), держит целевой throughput control point (по умолчанию 2%) и уменьшает кучу при снижении нагрузки. В статье описаны ограничения (одна куча на старте, влияние на startup и Gen2) и способы настройки.

Автор также разбирает формулу BCD (m ≈ 15000/√LDS), параметры GCDGen0GrowthPercent и GCDGen0GrowthMinFactor, и показывает, что включение DATAS снижает % pause time и общий размер кучи. Для диагностики доступны события SizeAdaptationTuning.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
7👍2
Roslyn можно использовать просто как библиотеку для анализа и генерации кода. Например, можно написать консольное приложение, которое загрузит solution, найдет нужные паттерны и перепишет код. В то время как Roslyn Analyzers привязаны к одному проекту, сам Roslyn как библиотека позволяет анализировать весь solution целиком.

Создадим консольное приложение и добавим нужные пакеты NuGet:

dotnet new console
dotnet add package Microsoft.Build.Locator
dotnet add package Microsoft.CodeAnalysis.CSharp
dotnet add package Microsoft.CodeAnalysis.CSharp.Workspaces
dotnet add package Microsoft.CodeAnalysis.Workspaces.Common
dotnet add package Microsoft.CodeAnalysis.Workspaces.MSBuild


Вот пример, как создать рабочее пространство Roslyn из решения:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;

// Находим сборки MSBuild в вашей системе
Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults();

// Возможно надо восстановить NuGet-пакеты решения перед тем, как открыть его в Roslyn.
// Зависимости могут потребоваться для корректного анализа.

// Создаём рабочее пространство Roslyn и загружаем решение
var ws = MSBuildWorkspace.Create();
var solution =
await ws.OpenSolutionAsync(@"my_solution.sln");


Теперь можно работать с решением. Вот несколько примеров.

Анализируем синтаксические деревья:

foreach (var proj in solution.Projects)
{
foreach (var doc in proj.Documents)
{
var syntree = await doc.GetSyntaxTreeAsync();
var semanticModel = await doc.GetSemanticModelAsync();
// Анализируем дерево
// Можно использовать CSharpSyntaxWalker для обхода
}
}


Получаем все ссылки на символ:

var comp = 
await solution.Projects.First().GetCompilationAsync();
var symbol =
comp.GetSpecialType(SpecialType.System_String);
var refs =
await SymbolFinder.FindReferencesAsync(symbol, solution);


Переименовываем символ

var symbol = comp
.GetSymbolsWithName(s => s == "MyMethod")
.Single();
var newSolution = await Renamer
.RenameSymbolAsync(
solution,
symbol,
new SymbolRenameOptions()
{
RenameOverloads = true
},
"MyMethod2");
ws.TryApplyChanges(newSolution);


Обновляем документ с помощью DocumentEditor

foreach (var proj in solution.Projects)
{
foreach (var doc in proj.Documents)
{
var editor =
await DocumentEditor.CreateAsync(doc);

// Изменяем
foreach(var emptyStatement in
editor
.OriginalRoot
.DescendantNodes()
.OfType<EmptyStatementSyntax>())
{
editor.RemoveNode(emptyStatement);
}

// Применяем изменения
var newDoc = editor.GetChangedDocument();
if (!ws.TryApplyChanges(newDoc.Project.Solution))
{
Console.WriteLine("Failed!");
}
}
}


Обновляем документ с помощью CSharpSyntaxRewriter:

foreach (var proj in solution.Projects)
{
foreach (var doc in proj.Documents)
{
// Изменяем
var root = await doc.GetSyntaxRootAsync();
if(root is null)
continue;

var newRoot =
new CustomRewriter().Visit(root);
var newDoc =
doc.WithSyntaxRoot(newRoot);

// Применяем изменения
if (!ws.TryApplyChanges(newDoc.Project.Solution))
{
Console.WriteLine("Failed!");
}
}
}

internal sealed class CustomRewriter :
CSharpSyntaxRewriter
{
public override SyntaxNode?
VisitIfStatement(IfStatementSyntax node)
=> node.WithLeadingTrivia(
SyntaxFactory
.ParseLeadingTrivia("/* comment */"));
}


Выберите любое «подопытное» решение, введите его путь вместо my_solution.sln, вставьте любой из примеров и можете пройти его в отладчике пошагово, чтобы посмотреть, как работает анализатор.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍52🤔1
Please open Telegram to view this post
VIEW IN TELEGRAM
😁20🥴1
0% разработчиков видят здесь проблему — пока не становится поздно.

Обычная выборка в EF Core с SELECT ... WHERE Id IN (...) выглядит безобидно. Всё работает… пока не перестаёт.

У каждой базы данных есть лимит на количество параметров в IN:

Oracle — 1000

SQL Server — несколько тысяч

Postgres / MySQL — ~64 000+

Если вы грузите тысячи ID через WHERE Id IN (...), вы всего в одном неудачном запросе от:

- краша в проде
- тихого усечения данных
- внезапных проблем с производительностью

Что делать вместо этого:
Делите ID на батчи и выполняйте несколько запросов
Используйте временные таблицы или table-valued параметры для массовых выборок
Всегда профилируйте запросы — не думайте, что они «и так потянут»

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🔥4
Изучаем CollectionsMarshal для Dictionary (.NET)

В отличие от ConcurrentDictionary, обычный Dictionary не имеет метода GetOrAdd.
Он нужен, чтобы добавить пару ключ–значение, если ключа ещё нет, или вернуть существующее значение, если он уже есть.

Наивная реализация выглядит так:

public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key, TValue value)
where TKey : notnull
{
if (dict.TryGetValue(key, out var result))
return result;

dict[key] = value;
return value;
}


Проблема → словарь обращается к ключу дважды: один раз для проверки наличия, второй — для вставки.
Это значит, что хеш-код и Equals вызываются минимум по два раза — неэффективно.

Более быстрый вариант с CollectionsMarshal

Метод CollectionsMarshal.GetValueRefOrAddDefault возвращает ссылку на значение по ключу и флаг, существует ли ключ.
Так как возвращается именно ref, значение можно менять напрямую:

public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key, TValue value)
where TKey : notnull
{
ref var dictionaryValue = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out var exists);
if (exists)
return dictionaryValue;

dictionaryValue = value;
return value;
}


Обновление значения с GetValueRefOrNullRef

CollectionsMarshal также предлагает метод GetValueRefOrNullRef, который позволяет обновить значение, если ключ уже существует:

public static bool TryUpdate<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key, TValue value)
where TKey : notnull
{
ref var dictionaryValue = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key);
if (!Unsafe.IsNullRef(ref dictionaryValue))
{
dictionaryValue = value;
return true;
}

return false;
}


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

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍7
Скрытая цена энтерпрайз-архитектуры на .NET

Ад адской отладки.

За 13+ лет в .NET-кодовых базах — одно и то же:

Команды строят фортификацию из абстракций вокруг проблем, которых у них нет.

IUserService → вызывает → IUserRepository
IUserRepository → оборачивает → IUserDataAccess
IUserDataAccess → дергает → IUserQueryBuilder
IUserQueryBuilder → наконец-то стучится → в базу

Чтобы поменять одно правило валидации → проходишь 5 слоев.
Чтобы починить баг → открываешь 7 файлов.

И оправдание всегда одно и то же:
«А вдруг нам придется заменить Entity Framework?»
«А вдруг мы поменяем базу?»
«А вдруг нам понадобится несколько реализаций?»

А вдруг то, а вдруг это.

Реальность такая:
99% этих "вдруг" никогда не происходят.

Я не видел ни одного проекта, где реально сменили ORM.
Зато видел десятки разработчиков, теряющих часы в лабиринтах абстракций.

Новые ребята не понимают, куда воткнуть новый кусок логики.
Сеньоры дебажат код, у которого слоев больше, чем у свадебного торта.

Итог?
Ты больше времени тратишь на навигацию, чем на разработку.

Хорошие абстракции прячут сложность.
Плохие → создают её.

А в большинстве энтерпрайз-.NET-проектов второго типа слишком много.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
💯444👎2👏2
Используй C# для работы с файлами Excel
(полностью бесплатно, кстати)

Если на машине установлен Excel (или Office 365, если уж на то пошло), то в системе уже есть встроенные библиотеки для программной работы со всеми приложениями Office.

Что удивительно, поиск в Google или даже через ИИ выдает устаревшие результаты, которые могут быть неактуальны. А лучший ответ от Perplexity ссылается на коммерческое решение, что чересчур для моих задач сегодня.

Но даже в 2025 году есть простой и понятный способ управлять Office из кода.

А LINQPad — идеальная среда для такого демо.

Добавь NuGet-ссылку на http://Microsoft.Office.Interop.Excel

Посмотри этот пример кода, чтобы понять, как:

- открыть книгу
- прочитать листы
- прочитать ячейки

Код создает невидимый процесс Excel для чтения и записи данных в таблицу. Минус в том, что Excel должен быть установлен на машине, поэтому этот способ не подходит для headless-автоматизации.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍4👎1
С EF Core можно делать забавные штуки.

Конфигурация сущности с:

- CHECK-ограничением Price > 0
- контролем точности столбца Price
- уникальным индексом на Name

Дальше генерим EF-миграцию и накатываем всё в базу.

Источник: читать

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Бенчмарк: чтение таблиц из C#

ClosedXML vs EPPlus
(FOSS против коммерческого решения)

Не стоит слишком углубляться в это.

LINQPad делает бенчмаркинг кода через BenchmarkDotNet до безумия простым.

Исходная таблица: 1000 строк и 2 колонки.
Колонка A: int
Колонка B: string

ClosedXML примерно на 29% быстрее в этом тесте.

С субъективной стороны — ClosedXML, на мой взгляд, проще в использовании.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
3👍2
Узнайте все о протоколе HTTP

Всё про самый используемый протокол в интернете. 😎

✓ Все коды состояния с пояснениями
✓ Заголовки и методы с примерами
✓ Инструменты для тестирования HTTP
✓ Понятная инфа про HTTP/3

https://http.dev

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
1
Сохрани себе нервы, когда работаешь с базой данных.

Всегда оборачивай UPDATE или DELETE в транзакцию с откатом — так ты защитишься от случайных косяков.

Лично я первым делом пишу:

begin transaction

rollback transaction


Теперь, если что-то пойдёт не так, изменения сразу откатятся при rollback.
Можно спокойно шлифовать запрос, не боясь напортачить.

Когда всё готово и уверен в результате = либо выделяешь нужный SQL и запускаешь напрямую,
либо просто меняешь rollback transaction на commit transaction.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21
Code Coverage теперь доступен в Visual Studio Community и Professional

Раньше эта функция была только в Enterprise-версии, но теперь любой разработчик может проверить, насколько хорошо его код покрыт тестами - прямо из IDE.

Больше не нужно ставить сторонние плагины или собирать отчёты вручную. Просто запускаешь тесты и Visual Studio показывает, какие участки кода реально выполнялись, а какие остались нетронутыми.

Отличный ход Microsoft 👍

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥16😁2