Операторы break и continue с метками в C# 👀
Сейчас это champion proposal
👉 @KodBlog
Сейчас это 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;
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯22👏6👍1
Одна строка кода для ускорения EF Core
Когда EF Core загружает связанные сущности через
Пример:
При 100 постах, у каждого по 10 комментариев, в каждом по 10 реакций — вместо 11 100 записей получаем результирующий набор на 10 000 строк.
Добавьте
Query Splitting это не хак и не костыль. Это правильный способ работы с EF Core в read-heavy сценариях.
👉 @KodBlog
Когда 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 сценариях.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🤯6❤2🔥1😁1
Публиковать доменные события до или после завершения транзакции?
По сути ты выбираешь между немедленной консистентностью и eventual consistency.
Я всегда публикую доменные события после коммита транзакции, желательно через Outbox.
А ты бы что выбрал и почему?
С EF interceptors можно творить реально интересные штуки.
Публикация доменных событий это всего лишь один из кейсов.
👉 @KodBlog
По сути ты выбираешь между немедленной консистентностью и eventual consistency.
Я всегда публикую доменные события после коммита транзакции, желательно через Outbox.
А ты бы что выбрал и почему?
С EF interceptors можно творить реально интересные штуки.
Публикация доменных событий это всего лишь один из кейсов.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
В .NET 10 JIT умеет убирать аллокацию из-за boxing при итерации по интерфейсам.
Но вот что еще круче: если GetEnumerator реализован через yield return, JIT в некоторых случаях тоже может это оптимизировать!
Классический пример: перебор RepeatedField<T>
Бенчмарк
👉 @KodBlog
Но вот что еще круче: если 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;
}
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10🔥3
.NET Reverse Proxy в 3 строки кода.
Этого достаточно, чтобы поднять YARP. Понятно, что настройки прокси нужно отдельно задать через appsettings.
Но все равно впечатляет, насколько легко быстро получить рабочий прокси.
Пробовал уже YARP?
Балансировка в YARP так же проста: по сути, просто обновляешь proxy settings.
Вот что нужно, чтобы стартануть
👉 @KodBlog
Этого достаточно, чтобы поднять YARP. Понятно, что настройки прокси нужно отдельно задать через appsettings.
Но все равно впечатляет, насколько легко быстро получить рабочий прокси.
Пробовал уже YARP?
Балансировка в YARP так же проста: по сути, просто обновляешь proxy settings.
Вот что нужно, чтобы стартануть
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤2🤨1
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
csharplang/proposals/collection-expression-arguments.md at main · dotnet/csharplang
The official repo for the design of the C# programming language - dotnet/csharplang
👎12🤯6👍5❤2
Совет по C#:
❌ if (x == null) { x = new List<string>(); }
✅ x ??= new List<string>();
Результат тот же, но в одну строку.
??= присваивает значение только если слева null.
Доступно начиная с C# 8.0.
Если вы предпочитате первый подход, это не значит что вы плохой разработчик😜
👉 @KodBlog
Результат тот же, но в одну строку.
??= присваивает значение только если слева null.
Доступно начиная с C# 8.0.
Если вы предпочитате первый подход, это не значит что вы плохой разработчик
Please open Telegram to view this post
VIEW IN TELEGRAM
❤21👍12😁4🥴2
Группировка данных
ToLookup это LINQ-метод, который группирует элементы по ключу. Похож на GroupBy, но есть пару нюансов, из-за которых он прям удобен в некоторых кейсах.
Он строит ILookup<TKey, TElement> это по сути готовая, неизменяемая коллекция групп. В каждой группе лежат элементы с одинаковым ключом.
Достаёшь группу через индексатор
ToLookup vs GroupBy:
GroupBy возвращает
ToLookup делает всё сразу и держит результат в памяти:
➡️ сгруппировал один раз при вызове
➡️ потом дергаешь группы сколько угодно, ничего не пересчитывается
➡️ но память под всю структуру выделяется сразу
Когда ToLookup заходит лучше всего
Используй, если нужно:
➡️ много раз обращаться к одним и тем же группам
➡️ не ловить лишние пересчёты группировки
➡️ получать пустую выборку вместо исключений при неизвестном ключе
➡️ иметь неизменяемую структуру
Не лучший выбор, если:
➡️ данных очень много и жалко память
➡️ нужна ленивость и потоковая обработка
➡️ группировка нужна один раз, тогда проще и дешевле GroupBy
👉 @KodBlog
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 заходит лучше всего
Используй, если нужно:
Не лучший выбор, если:
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍6
Забавный трюк для .NET: как в рантайме узнать, в каком файле и на какой строке объявлен метод
Почему не подходят
Потому что они работают только на месте вызова и требуют менять сигнатуры методов. А если тебе нужно взять локацию *любого*
Идея: Используем Portable PDB: в PDB лежит маппинг IL → исходники (файл, строки, колонки) через sequence points.
Настройка
Обычно portable PDB генерятся по умолчанию, но можно явно:
▪️ отдельным файлом:
▪️ или вшить в сборку:
Реализация (суть)
Читаем PE, ищем embedded pdb, если нет — пытаемся открыть
Минимальный скелет:
Пример использования:
Важный нюанс: в проде PDB могут быть не задеплоены специально (безопасность/размер/политики), так что всегда держим
Источник
@KodBlog
Почему не подходят
[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
👍6❤1
Расширяющий метод Entity Framework вместо TagWith.
👉 @KodBlog
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();
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8👏3🔥2
Пожалуйста, пересмотрите решение и всё-таки разрешите асинхронную валидацию моделей 👀
Сейчас это висит в майлстоуне “.NET 11 Planning”, очень хотелось бы, чтобы фичу реально довели до реализации.
@KodBlog
Сейчас это висит в майлстоуне “.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
Это бесплатный дизайнер схем, который работает на 100% локально, даже офлайн. Можно собирать как простые, так и сложные схемы, и ничего не улетает в облако.
Поддерживает импорт существующих схем, визуальное проектирование с нуля и экспорт результата в разные форматы и даже в код.
https://relatedb.com/
Please open Telegram to view this post
VIEW IN TELEGRAM
❤18🔥3
OpenTelemetry + .NET: трейсы, метрики и логи в SigNoz
В статье показано развёртывание через docker-compose, генерация быстрых/медленных/ошибочных запросов и просмотр коррелированных трейсов, метрик и логов в SigNoz. Описаны настройки .NET (ResourceBuilder, ActivitySource, авто‑инструментирование, OTLP) и практические советы; исходники в репо.
👉 @KodBlog
В статье показано развёртывание через docker-compose, генерация быстрых/медленных/ошибочных запросов и просмотр коррелированных трейсов, метрик и логов в SigNoz. Описаны настройки .NET (ResourceBuilder, ActivitySource, авто‑инструментирование, OTLP) и практические советы; исходники в репо.
Please open Telegram to view this post
VIEW IN TELEGRAM
Хабр
Наблюдаемость .NET-сервисов с помощью OpenTelemetry (traces/metrics/logs). Практический пример
Привет, Хабр! TL;DR: Поднимаем стенд в docker-compose (gateway + api + postgres + otel-collector + SigNoz/ClickHouse). Делаем 3 запроса: быстрый / медленный / с исключением. Смотрим в SigNoz трейсы...
Хватит дергать DateTime.UtcNow прямо в бизнес-логике.
Строчка выглядит безобидно, но именно из-за нее тесты потом превращаются в кошмар
Вот типичная ловушка:
* DateTime.UtcNow > user.SubscriptionEnd
Вроде ок, да?
Но как только пытаешься это протестировать, вылезает проблема:
- ты не контролируешь DateTime.UtcNow
- тесты становятся флаки или обрастают костылями
- бизнес-правила оказываются привязаны к инфраструктуре
Есть варианты лучше:
1. Передавать DateTime параметром. Самый простой путь, отлично заходит для маленьких методов.
2. Вынести время в интерфейс IClock. Масштабируется лучше и дружит с тестами.
Когда ты инвертируешь зависимость от времени, доменная логика становится:
✅ проще тестировать
✅ более детерминированной
✅ с нормальным разделением ответственности
В следующий раз, когда пишешь условие, завязанное на время, остановись и спроси:
“Мне нужно будет это тестировать?”
Если да, выноси часы наружу.
@KodBlog
Строчка выглядит безобидно, но именно из-за нее тесты потом превращаются в кошмар
Вот типичная ловушка:
* DateTime.UtcNow > user.SubscriptionEnd
Вроде ок, да?
Но как только пытаешься это протестировать, вылезает проблема:
- ты не контролируешь DateTime.UtcNow
- тесты становятся флаки или обрастают костылями
- бизнес-правила оказываются привязаны к инфраструктуре
Есть варианты лучше:
1. Передавать DateTime параметром. Самый простой путь, отлично заходит для маленьких методов.
2. Вынести время в интерфейс IClock. Масштабируется лучше и дружит с тестами.
Когда ты инвертируешь зависимость от времени, доменная логика становится:
В следующий раз, когда пишешь условие, завязанное на время, остановись и спроси:
“Мне нужно будет это тестировать?”
Если да, выноси часы наружу.
@KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🥴9🔥3❤1👍1
Не пиши в SQL = NULL или != NULL
Для NULL всегда используй IS NULL / IS NOT NULL
NULL это “нет значения / неизвестно”, и обычные операторы сравнения (=, !=, <, >) с ним не работают, потому что NULL не равен, не больше и не меньше вообще ничего.
= / != сравнивают значения. А NULL это не значение, а отсутствие значения. Поэтому IS / IS NOT проверяют именно наличие или отсутствие значения.
👉 @KodBlog
Для NULL всегда используй IS NULL / IS NOT NULL
NULL это “нет значения / неизвестно”, и обычные операторы сравнения (=, !=, <, >) с ним не работают, потому что NULL не равен, не больше и не меньше вообще ничего.
= / != сравнивают значения. А NULL это не значение, а отсутствие значения. Поэтому IS / IS NOT проверяют именно наличие или отсутствие значения.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍23🔥3🥴3
Лучший конфиг для HttpClient это тот, который ты не копируешь в каждом сервисе.
В .NET ты настраиваешь все один раз через IHttpClientFactory (и Aspire, если используешь):
* service discovery для вызовов сервис-сервис
* дефолтную resiliency-настройку, чтобы кратковременные фейлы не ломали тебе жизнь
Дальше просто инжектишь клиент и пишешь бизнес-код.
- полный разбор
👉 @KodBlog
В .NET ты настраиваешь все один раз через IHttpClientFactory (и Aspire, если используешь):
* service discovery для вызовов сервис-сервис
* дефолтную resiliency-настройку, чтобы кратковременные фейлы не ломали тебе жизнь
Дальше просто инжектишь клиент и пишешь бизнес-код.
- полный разбор
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
Покрывает штуки, которые реально важны и неочевидны (типа: как тестировать транзакционные письма в ASP.NET Core?) и вообще помогает улучшать вывод LLM (типы!).
github.com/Aaronontheweb/dotnet-skills?tab=readme-ov-file
Все это про то, чтобы Claude делал вещи нормально без того, чтобы ты на него орал.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍2🥴2
БОЛЬШОЙ SQL грех
Никогда не пихай ORDER BY внутрь CTE (или подзапроса), если там же рядом нет LIMIT.
Если LIMIT забыть, база обычно будет сортировать всю таблицу вообще без смысла.
👉 @KodBlog
Никогда не пихай ORDER BY внутрь CTE (или подзапроса), если там же рядом нет LIMIT.
Если LIMIT забыть, база обычно будет сортировать всю таблицу вообще без смысла.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤3👏2👎1
Как пользоваться LINQ
Чтобы использовать LINQ, нужно подключить
➡️ Фильтрация через Where
Если использовать
Результат вывода ниже будет
➡️ Цикл через ForEach
Если использовать
Результат вывода ниже будет
➡️ Комбинации
LINQ при правильной комбинации может сильно сокращать код.
В примере ниже из
➡️ Пошаговая обработка через Select
В коде ниже каждый элемент массива строк по одному конвертируется в
(В других языках это что-то вроде
➡️ Any как OR-условие
В коде ниже проверяем, есть ли в списке имён хотя бы одно, которое начинается с
➡️ GetRange: взять диапазон элементов
В примере ниже при
➡️ Reverse: развернуть список
В коде ниже изначально список
➡️ Single: получить единственный элемент
В коде ниже пытаемся через
Для
➡️ Contains: проверить наличие элемента
В коде ниже проверяем, есть ли в списке
👉 @KodBlog
Чтобы использовать LINQ, нужно подключить
System.Linq через using.Если использовать
Where, можно коротко вытащить из массива/списка только элементы, которые подходят под условие.Результат вывода ниже будет
1.// список int
List<int> idList = new List<int>{1, 2, 3};
// фильтруем: берём только те, где 1
IEnumerable<int> selectList = idList.Where(id => id == 1);
Если использовать
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 позволяет легко обработать элементы массива по порядку.В коде ниже каждый элемент массива строк по одному конвертируется в
int, а затем весь результат превращается в List<int>.(В других языках это что-то вроде
Map: берём элементы массива и преобразуем)// массив String
string[] idString = {"1", "2", "3"};
// делаем int и превращаем в List
List<int> idList = idString.Select(id => int.Parse(id)).ToList();
Any возвращает true, если хотя бы один элемент удовлетворяет условию.В коде ниже проверяем, есть ли в списке имён хотя бы одно, которое начинается с
"A".// List строк
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
// Alice подходит, значит True
names.Any(name => name.StartsWith("A"));
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 разворачивает порядок элементов в списке.В коде ниже изначально список
"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 позволяет получить элемент, который встречается ровно один раз.В коде ниже пытаемся через
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 позволяет проверить, есть ли элемент в списке.В коде ниже проверяем, есть ли в списке
"Alice, Bob, Charlie" значение "Alice".// List строк
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
// Alice есть в списке, значит True
names.Contains(name => name.StartsWith("Alice"));
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17🥰3
Фанфакт: в C# можно руками создать окно через Win32 API, прям как в C++.
По ощущениям это реально то же самое, что и в C++: регаешь window class, делаешь
Что в итоге получаешь: очень лёгкую штуку без лишнего оверхеда. Если использовать
И да, туда же спокойно добавляются кнопки и прочие контролы через тот же Win32. Выглядит олдскульно, но зато железобетонно понимаешь, как GUI под капотом работает.
👉 @KodBlog
По ощущениям это реально то же самое, что и в C++: регаешь window class, делаешь
CreateWindowEx, заводишь WndProc, гоняешь GetMessage/DispatchMessage. Практической пользы почти ноль по сравнению с WinForms, но поиграться прикольно :DЧто в итоге получаешь: очень лёгкую штуку без лишнего оверхеда. Если использовать
LibraryImport + NativeAOT (это быстрее и приятнее, чем классический P/Invoke), можно собрать примерно 1MB exe без DLL, который моментально стартует и открывает окно. Делать так в проде обычно незачем, это скорее чисто эксперимент.И да, туда же спокойно добавляются кнопки и прочие контролы через тот же Win32. Выглядит олдскульно, но зато железобетонно понимаешь, как GUI под капотом работает.
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔8👍4❤3👎1🤯1