Библиотека шарписта | C#, F#, .NET, ASP.NET
Все самое полезное для C#-разработчика в одном канале. По рекламе: @proglib_adv Учиться у нас: https://proglib.io/w/b60af5a4 Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Show more📈 Analytical overview of Telegram channel Библиотека шарписта | C#, F#, .NET, ASP.NET
Channel Библиотека шарписта | C#, F#, .NET, ASP.NET (@csharpproglib) in the Russian language segment is an active participant. Currently, the community unites 21 865 subscribers, ranking 6 209 in the Technologies & Applications category and 30 824 in the Russia region.
📊 Audience metrics and dynamics
Since its creation on невідомо, the project has demonstrated rapid growth, gathering an audience of 21 865 subscribers.
According to the latest data from 11 June, 2026, the channel demonstrates stable activity. Although there has been a change in the number of participants by -95 over the last 30 days and by -6 over the last 24 hours, overall reach remains high.
- Verification status: Not verified
- Engagement rate (ER): The average audience engagement rate is 12.48%. Within the first 24 hours after publication, content typically collects 7.13% reactions from the total number of subscribers.
- Post reach: On average, each post receives 2 729 views. Within the first day, a publication typically gains 1 560 views.
- Reactions and interaction: The audience actively supports content: the average number of reactions per post is 9.
- Thematic interests: Content is focused on key topics such as .net, шарписта, навигация, await, string.
📝 Description and content policy
The author describes the resource as a platform for expressing subjective opinions:
“Все самое полезное для C#-разработчика в одном канале.
По рекламе: @proglib_adv
Учиться у нас: https://proglib.io/w/b60af5a4
Для обратной связи: @proglibrary_feeedback_bot
РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead”
Thanks to the high frequency of updates (latest data received on 12 June, 2026), the channel maintains relevance and a high level of publication reach. Analytics show that the audience actively interacts with content, making it an important point of influence in the Technologies & Applications category.
if (value == null) throw new ArgumentNullException(...) в каждом методе, кто-то тащит FluentValidation и настраивает его под свои нужды. OrionGuard предлагает ещё один вариант: fluent-интерфейс, поддержку ASP.NET Core, MediatR, Blazor, gRPC и SignalR. Всё в одной экосистеме.
Установка базового пакета:
dotnet add package OrionGuardПростая валидация выглядят так:
using Moongazing.OrionGuard.Core;
using Moongazing.OrionGuard.Extensions;
Ensure.That(email).NotNull().NotEmpty().Email();
Ensure.That(age).InRange(18, 120);
Если нужна производительность без аллокаций, то есть FastGuard на основе Span<T>:
FastGuard.NotNullOrEmpty(name, nameof(name));
FastGuard.Email(email, nameof(email));
Что внутри
Библиотека разбита на 9 пакетов. Каждый подключается отдельно.
OrionGuard — ядро. OrionGuard.AspNetCore — middleware, фильтры, интеграция с IOptions. OrionGuard.MediatR — автоматическая валидация в CQRS-пайплайне. OrionGuard.Generators — source-генераторы для компайл-тайм валидации без рефлексии. OrionGuard.Blazor — интеграция с EditForm. OrionGuard.Grpc и OrionGuard.SignalR — перехватчики для gRPC и SignalR.
Несколько конкретных примеров
Накопление ошибок без исключений:
var result = GuardResult.Combine(
Ensure.Accumulate(email, "Email").NotNull().Email().ToResult(),
Ensure.Accumulate(password, "Password").MinLength(8).ToResult()
);
if (result.IsInvalid)
return BadRequest(result.ToErrorDictionary());
Защита от SQL-инъекций и XSS:
userInput.AgainstSqlInjection(nameof(userInput));
userInput.AgainstXss(nameof(userInput));
filePath.AgainstPathTraversal(nameof(filePath));
Вложенная валидация с путями до поля:
var result = Validate.Nested(order)
.Property(o => o.OrderNumber, p => p.NotEmpty())
.Nested(o => o.Customer, customer => customer
.Property(c => c.Email, p => p.NotEmpty().Email())
.Nested(c => c.Address, address => address
.Property(a => a.ZipCode, p => p.NotEmpty())))
.Collection(o => o.Items, (item, _) => item
.Property(i => i.Quantity, p => p.GreaterThan(0)))
.ToResult();
// Ошибки будут выглядеть так: "Customer.Address.ZipCode", "Items[2].Quantity"
Динамические правила из JSON. Для случаев, когда правила хранятся в базе или конфиге:
var json = """
{
"Rules": [
{ "PropertyName": "Email", "RuleType": "Email" },
{ "PropertyName": "Age", "RuleType": "Range", "Parameters": { "Min": 18, "Max": 120 } }
]
}
""";
var validator = DynamicValidator.FromJson(json);
var result = validator.Validate(userDto);
Source-генератор для NativeAOT:
[GenerateValidator]
public sealed class CreateUserRequest
{
[NotNull, NotEmpty, Length(3, 50)]
public string Name { get; set; }
[NotNull, Email]
public string Email { get; set; }
}
// Валидатор генерируется на этапе компиляции — без рефлексии
var result = CreateUserRequestValidator.Validate(request);
Интеграция с ASP.NET Core:
// Program.cs
builder.Services.AddOrionGuardAspNetCore();
app.MapPost("/api/users", (CreateUserRequest req) => { ... })
.WithValidation<CreateUserRequest>();
Библиотека поддерживает 14 языков для сообщений об ошибках, включая русский. Есть слой совместимости с FluentValidation, миграция сводится к замене using. Все regex-паттерны генерируются через GeneratedRegex, FrozenSet используется для O(1)-поиска в security-паттернах. Последний релиз v6.2.0 вышел 22 апреля 2026.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewvar data = httpClient.GetStringAsync(url).Result; // блокирует поток
Task.Run(() => DoWork()).Wait(); // форсированная синхронизация
Эти вызовы берут асинхронную операцию и намеренно блокируют поток до её завершения. В результате поток занят, но не делает ничего полезного, а просто ждёт.
Попробуйте сделать так и замеряйте результат:
var data = await httpClient.GetStringAsync(url);
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминаторnode-gyp и конкретной версии Python на каждой машине разработчика. Для .NET-команды это лишняя зависимость, которую нужно поддерживать и в CI, и при онбординге новых участников.
Решение
Native AOT умеет компилировать .NET-код в нативную shared library с произвольными точками входа. Node.js аддоны требуют только одного: экспортированной функции napi_register_module_v1. Native AOT с этим справляется.
Как это работает
Аддон взаимодействует с Node.js через [N-API](https://nodejs.org/api/n-api.html) — стабильный C-совместимый интерфейс. Язык реализации значения не имеет, важно лишь соответствие сигнатурам.
Минимальный .csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<PublishAot>true</PublishAot>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
PublishAot говорит SDK собрать нативную библиотеку при публикации. AllowUnsafeBlocks нужен из-за работы с указателями при интеропе с N-API.
Точка входа модуля:
[UnmanagedCallersOnly(
EntryPoint = "napi_register_module_v1",
CallConvs = [typeof(CallConvCdecl)])]
public static nint Init(nint env, nint exports)
{
Initialize();
RegisterFunction(env, exports, "readStringValue"u8, &ReadStringValue);
return exports;
}
Атрибут [UnmanagedCallersOnly] экспортирует метод с нужным именем и calling convention. Суффикс u8 создаёт ReadOnlySpan<byte> с UTF-8 строкой без аллокаций.
Вызов N-API из .NET
N-API функции экспортирует сам node.exe. Чтобы не линковаться с отдельной библиотекой, используется кастомный резолвер через NativeLibrary.SetDllImportResolver:
NativeLibrary.SetDllImportResolver(
Assembly.GetExecutingAssembly(),
(libraryName, assembly, searchPath) =>
libraryName is "node"
? NativeLibrary.GetMainProgramHandle()
: 0);
После этого P/Invoke с именем "node" будет резолвиться в хост-процесс:
[LibraryImport("node", EntryPoint = "napi_create_string_utf8")]
internal static partial Status CreateStringUtf8(
nint env, ReadOnlySpan<byte> str, nuint length, out nint result);
Пример экспортируемой функции
Функция читает значение из реестра Windows и возвращает его в JavaScript:
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static nint ReadStringValue(nint env, nint info)
{
try
{
var keyPath = GetStringArg(env, info, 0);
var valueName = GetStringArg(env, info, 1);
if (keyPath is null || valueName is null)
{
ThrowError(env, "Expected two string arguments: keyPath, valueName");
return 0;
}
using var key = Registry.CurrentUser.OpenSubKey(keyPath, writable: false);
return key?.GetValue(valueName) is string value
? CreateString(env, value)
: GetUndefined(env);
}
catch (Exception ex)
{
ThrowError(env, $"Registry read failed: {ex.Message}");
return 0;
}
}
Исключения нужно обязательно перехватывать: необработанное исключение в методе с [UnmanagedCallersOnly] крашит хост-процесс. ThrowError пробрасывает ошибку в JavaScript как стандартный Error.
Что в итоге
Команда убрала зависимость от Python и node-gyp. Теперь yarn install работает только с Node.js и .NET SDK, которые и так нужны для разработки. CI стал проще, онбординг быстрее.
Производительность сопоставима с C++-реализацией: Native AOT генерирует оптимизированный нативный код, а для задач типа доступа к реестру и маршалинга строк разницы на практике нет.
➡️ Источник
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewMicrosoft.AspNetCore.DataProtection, который попал в релиз вместе с плановым обновлением 10.0.6.
Что случилось
После выхода 10.0.6 пользователи начали сообщать, что расшифровка данных перестала работать. Microsoft изучила проблему и обнаружила, что регрессия также открывает уязвимость CVE-2026-40372.
Суть бага: управляемый энкриптор вычислял HMAC-тег не над теми байтами полезной нагрузки, а потом и вовсе выбрасывал результат. Это приводило к повышению привилегий. Затронуты версии Microsoft.AspNetCore.DataProtection от 10.0.0 до 10.0.6 включительно.
Кого это касается
Всех, кто использует ASP.NET Core Data Protection в приложениях на .NET 10. Это стандартный механизм защиты данных в ASP.NET Core — сессии, антифоргери-токены, куки и всё, что шифруется через IDataProtector.
Что делать
Обновить пакет Microsoft.AspNetCore.DataProtection до версии 10.0.7 как можно скорее:
dotnet add package Microsoft.AspNetCore.DataProtection --version 10.0.7
После обновления пакета пересобрать и перевыложить приложение. Проверить текущую версию SDK можно командой:
dotnet --info
➡️ Источник
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#async_newsКак эффективно управлять контекстным окном LLM в мультиагентных системах и не сливать бюджет на токеныВ кружке Кирилл рассказал, какие именно подходы будем разбирать. 👉 Занять место на вебинаре
<maps:Map IsClusteringEnabled="True" />
Ближайшие метки автоматически группируются в кластер с числом внутри. При увеличении масштаба кластер раскрывается до отдельных пинов.
Разные группы кластеров
Если нужно, чтобы кофейни кластеризовались отдельно от парков, используем ClusteringIdentifier на Pin:
map.Pins.Add(new Pin
{
Label = "Pike Place Coffee",
Location = new Location(47.6097, -122.3331),
ClusteringIdentifier = "coffee"
});
map.Pins.Add(new Pin
{
Label = "Occidental Square",
Location = new Location(47.6064, -122.3325),
ClusteringIdentifier = "parks"
});
Пины с одинаковым идентификатором группируются вместе. С разными — образуют независимые кластеры, даже если географически рядом. Пины без идентификатора попадают в общую группу по умолчанию.
Обработка нажатия на кластер
При тапе на кластер срабатывает событие ClusterClicked:
map.ClusterClicked += async (sender, e) =>
{
string names = string.Join("\n", e.Pins.Select(p => p.Label));
await DisplayAlert(
$"Кластер ({e.Pins.Count} меток)",
names,
"OK");
// Отменить стандартное приближение к кластеру:
// e.Handled = true;
};
В ClusterClickedEventArgs три поля:
• Pins — список пинов в кластере
• Location — географический центр
• Handled — если true, стандартное поведение (зум к кластеру) отменяется.
Платформенные детали
На Android кластеризация работает через собственный алгоритм на основе сетки, пересчитывается при смене масштаба. Сторонние зависимости не нужны.
На iOS и Mac Catalyst используется нативный MKClusterAnnotation из MapKit — анимации и поведение платформенные.
Пример с кластеризацией уже есть в репозитории, страница Clustering в проекте MapDemo.
➡️ Блог разработчиков
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминаторis или as быстро превращается в шаблонный код. OfType<T> решает это в одну строку.
Пример:
var mixedList = new List<object> { 1, "hello", 2.5, "world", 3, true };
var stringsOnly = mixedList.OfType<string>();
// Результат: hello, world
Из шести элементов метод выбирает только строки. Числа, булево значение и double игнорируются.
Как это работает под капотом
OfType<T> использует оператор is для каждого элемента. Примерно так выглядит его реализация:
public static IEnumerable<T> OfType<T>(this IEnumerable source)
{
foreach (var item in source)
{
if (item is T typed)
yield return typed;
}
}
Метод ленивый: перебор происходит только при итерации по результату, не сразу при вызове.
Когда использовать
Удобно при работе с IEnumerable<object>, коллекциями ArrayList из старого кода, или когда нужно вытащить конкретный тип из гетерогенной иерархии объектов.
var textBoxes = Controls.OfType<TextBox>();Чем отличается от
Cast<T>
Cast<T> бросает InvalidCastException, если встречает несовместимый тип. OfType<T> просто пропускает такой элемент. Если коллекция точно однородная и ошибка должна сигнализировать о проблеме, уместен Cast<T>. В остальных случаях безопаснее OfType<T>.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewsudo перед командой. На Windows для этого приходилось открывать новый терминал от имени администратора, делать правый клик, подтверждать UAC. Microsoft наконец добавила sudo в Windows 11 нативно, начиная со сборки 26045.
Что это такое
Sudo for Windows позволяет запускать команды с правами администратора прямо из обычного терминала, без переоткрытия сессии. Это не порт Unix-утилиты, а отдельная Windows-реализация той же идеи. Некоторые детали поведения отличаются от привычного sudo на Linux.
Как включить
Через настройки: Параметры → Система → Для разработчиков → Включить sudo.
Или через командную строку с правами администратора:
sudo config --enable normal
Три режима работы
forceNewWindow — режим по умолчанию. Команда запускается в новом окне с правами администратора. Похоже на runas, но удобнее.
disableInput — команда выполняется в текущем окне, но без возможности ввода. Подходит для автоматических задач, где интерактивный ввод не нужен.
normal — полный инлайн-режим, как на Linux. Команда запускается в текущем окне и может принимать ввод. Наиболее удобный вариант, но несёт больше рисков безопасности.
Переключение между режимами:
sudo config --enable forceNewWindow
sudo config --enable disableInput
sudo config --enable normal
Примеры использования
Редактирование системного файла:
sudo notepad C:\Windows\System32\drivers\etc\hosts
Установка глобального npm-пакета:
sudo npm install -g package-name
Проверка сетевых соединений:
sudo netstat -ab
Про безопасность
При запуске через sudo появляется UAC-запрос на подтверждение. Режимы disableInput и normal несут дополнительные риски: нежелательный процесс с низкими правами теоретически может взаимодействовать с повышенным процессом. Для большинства сценариев рекомендован forceNewWindow.
Что нужно учесть
Инструмент доступен только на Windows 11, начиная с версии 24H2. Управлять им можно через настройки, командную строку или реестр (HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Sudo). Есть и GPO-политика для корпоративного управления.
➡️ Репозиторий
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewDbContext. Тесты требуют реальной базы или громоздких моков. При смене ORM надо переписывать половину приложения.
Репозиторий даёт одну точку входа для работы с конкретной сущностью и позволяет подменять реализацию без изменения вызывающего кода.
Неочевидный момент 1: SaveChanges внутри репозитория
Если вызывать SaveChangesAsync() в каждом методе репозитория, теряется возможность атомарно сохранить несколько операций. Например, создать пользователя и сразу выдать ему роль в одной транзакции не получится.
Решение: убрать SaveChanges из репозитория и перенести его в Unit of Work или вызывать явно на уровне сервиса.
public interface IUnitOfWork : IDisposable
{
IUserRepository Users { get; }
IRoleRepository Roles { get; }
Task<int> SaveChangesAsync();
}
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
public IUserRepository Users { get; }
public IRoleRepository Roles { get; }
public UnitOfWork(AppDbContext context)
{
_context = context;
Users = new UserRepository(context);
Roles = new RoleRepository(context);
}
public async Task<int> SaveChangesAsync()
=> await _context.SaveChangesAsync();
public void Dispose() => _context.Dispose();
}
Неочевидный момент 2: Generic-репозиторий не всегда помогает
Соблазн велик написать один GenericRepository<T> и использовать везде:
public class GenericRepository<T> : IRepository<T> where T : class
{
protected readonly AppDbContext _context;
protected readonly DbSet<T> _dbSet;
public GenericRepository(AppDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();
public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);
public void Update(T entity) => _dbSet.Update(entity);
public void Delete(T entity) => _dbSet.Remove(entity);
}
Проблема в том, что специфичные запросы всё равно придётся добавлять. Найти пользователя по почте через универсальный метод не выйдет без дополнительных расширений. В итоге generic-репозиторий обрастает методами и теряет смысл.
Лучше наследовать от него там, где действительно нет специфики, а сложные сущности описывать отдельными интерфейсами:
public interface IUserRepository : IRepository<User>
{
Task<User?> GetByEmailAsync(string email);
Task<IEnumerable<User>> GetActiveUsersAsync();
}
public class UserRepository : GenericRepository<User>, IUserRepository
{
public UserRepository(AppDbContext context) : base(context) { }
public async Task<User?> GetByEmailAsync(string email)
=> await _dbSet.FirstOrDefaultAsync(u => u.Email == email);
public async Task<IEnumerable<User>> GetActiveUsersAsync()
=> await _dbSet.Where(u => u.IsActive).ToListAsync();
}
Неочевидный момент 3: EF Core уже репозиторий
DbContext и DbSet<T> это и есть реализация паттерна репозиторий. DbSet — коллекция объектов, DbContext — Unit of Work. Оборачивать EF Core в ещё один слой репозитория иногда только добавляет лишний код.
Это оправдано, когда нужно:
- тестировать бизнес-логику без базы (мок интерфейса проще, чем мок DbContext)
- поддерживать несколько источников данных
- изолировать слои приложения архитектурно
Если проект небольшой и планов менять ORM нет, прямое использование DbContext в сервисах может быть вполне нормальным решением.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминаторTask.Run или городите велосипед поверх IHostedService, знакомьтесь с BusyBee. Это небольшая библиотека для in-memory обработки фоновых задач в .NET, которая построена на нативных каналах (System.Threading.Channels).
Какую проблему решает
Хочется просто поставить задачу в очередь и не думать об управлении потоками, таймаутах и наблюдаемости. Стандартные средства .NET это умеют, но требуют немало бойлерплейта. Hangfire или Quartz — тяжеловато, если нужно только in-memory без персистентности.
Что умеет BusyBee
Ограниченные и неограниченные очереди с настройкой поведения при переполнении: можно выбросить исключение, подождать, отбросить самую старую или новую задачу. Параллельное выполнение задач с настраиваемым пулом слотов. Глобальные и per-job таймауты. Интеграция с OpenTelemetry для трассировки и метрик. Полная поддержка DI и CancellationToken.
Как подключить:
dotnet add package BusyBeeРегистрация в DI:
builder.Services
.AddBusyBee()
.WithUnboundedQueue()
.WithGlobalJobTimeout(TimeSpan.FromSeconds(30))
.WithLevelOfParallelism(10);
Поставить задачу в очередь:
await queue.Enqueue(async (services, context, cancellationToken) =>
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Обрабатываем задачу {JobId}", context.JobId);
await Task.Delay(1000, cancellationToken);
}, cancellationToken);
Каждая задача получает контекст с JobId, временем постановки в очередь и временем старта. Через services доступны любые зарегистрированные сервисы.
Ограниченная очередь:
builder.Services
.AddBusyBee()
.WithBoundedQueue(capacity: 1000, OverflowStrategy.DropOldest);
Подключение OpenTelemetry:
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource(BusyBee.Observability.TracingConstants.TraceSourceName)
.AddConsoleExporter())
.WithMetrics(metrics => metrics
.AddMeter(BusyBee.Observability.MetricsConstants.MeterName)
.AddPrometheusExporter());
Обработка ошибок
Можно подключить собственные обработчики для сбоев, таймаутов и ручной отмены задач через реализацию интерфейсов IJobFailureHandler, IJobTimeoutHandler и IManualCancellationHandler:
services.AddBusyBee()
.WithJobFailureHandler<MyCustomJobFailureHandler>();
BusyBee подойдёт, когда нужен простой in-memory обработчик в фоне без внешних зависимостей и баз данных.
➡️ Репозиторий
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewЗачем глубоко копать мультиагентные системы, если можно обойтись старым добрым кодом? Как контролировать расходы на токены, чтобы новая фича не разорила бизнес? Как заставить агента работать стабильно и предсказуемо, а не галлюцинировать?Эту инженерную часть мы и будем разбирать на курсе. Будем учиться интегрировать внешние API, работать с RAG, LangGraph, CrewAI и деплоить всё это так, чтобы работало как часы. Стартуем завтра. Для участия и доступа к программе переходите по ссылке.
allows ref struct из C# 13 и extension-членов из C# 14. Также подтянули поддержку InlineArray из C# 12 — на удивление поздно, но лучше поздно чем никогда. Детекция паттернов обновлена до Roslyn 5.0 RTM.
Новое в интерфейсе
Появился выделенный UI для работы с C# 14 extension-членами. Добавлена навигация по видимой истории — теперь можно перемещаться по посещённым узлам в дереве сборок. Из поставки убрали переводы интерфейса — проект сосредоточился на английском.
API diff
Одна из самых интересных новинок. ILSpy теперь умеет сравнивать публичное API двух сборок и показывать разницу. Полезно при анализе изменений между версиями библиотек, когда исходников нет.
Улучшения декомпилятора
Исправлена корректная декомпиляция pre-increment операторов, unmanaged function pointers и локальных функций с параметрами по умолчанию. Улучшено определение record-типов и primary constructors. CSharpConversions получил общие улучшения для правил C# вплоть до версии 9. Дизассемблер теперь поддерживает формат ildasm /caverbal.
Пакетная генерация PDB
Новый метод GeneratePdbForAssemblies заменил старый GeneratePdbForAssembly — теперь можно генерировать PDB для нескольких сборок за один вызов.
Чистка API
Удалены устаревшие типы: UnresolvedUsingScope, ToTypeReference, ITypeReference. ResolvedUsingScope переименован в UsingScope. Если вы используете ICSharpCode.Decompiler как NuGet-пакет в своих инструментах — стоит проверить совместимость.
Производительность
Убрана XML-сериализация из DecompilerSettings — настройки теперь сохраняются быстрее.
Важное предупреждение
Команда отдельно предупреждает: домен ilspy.org им не принадлежит. Скачивать ILSpy нужно только с GitHub Releases.
➡️ Источник
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#async_news
Available now! Telegram Research 2025 — the year's key insights 
