Библиотека шарписта | C#, F#, .NET, ASP.NET
21.9K subscribers
2.83K photos
41 videos
85 files
5.24K links
Все самое полезное для C#-разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/b60af5a4

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Download Telegram
🥸 Подборка вакансий для шарпистов

C# Разработчик — удалёнка.

.NET-разработчик (Senior) — удалёнка.

Разработчик C# (.NET) — тоже удалёнка.

➡️ Еще больше топовых вакансий — в нашем канале C# Jobs

🐸 Библиотека шарписта
Please open Telegram to view this post
VIEW IN TELEGRAM
👨‍💻 Паттерн стратегия в C#. Когда он нужен, а когда нет

Стратегия инкапсулирует семейство алгоритмов и позволяет подменять их на лету. Звучит полезно. На практике это часто превращается в десятки файлов ради замены одной функции. В современном C# для этого есть средства попроще.

Классическая реализация

Стандартный подход выглядит так. Интерфейс ISortStrategy, пять конкретных реализаций, класс-контекст, который принимает стратегию и вызывает её метод.

public interface ISortStrategy
{
void Sort(List<int> list);
}

public class BubbleSortStrategy : ISortStrategy
{
public void Sort(List<int> list)
{
// реализация пузырьковой сортировки
}
}

public class QuickSortStrategy : ISortStrategy
{
public void Sort(List<int> list)
{
// реализация быстрой сортировки
}
}

public class SortContext
{
private ISortStrategy _strategy;

public SortContext(ISortStrategy strategy)
{
_strategy = strategy;
}

public void SetStrategy(ISortStrategy strategy)
{
_strategy = strategy;
}

public void ExecuteSort(List<int> list)
{
_strategy.Sort(list);
}
}


Для каждого нового алгоритма мы создаём отдельный класс. Контекст ничего не знает о конкретной реализации и работает через интерфейс. Всё по канону GoF.

Проблема в том, что для простых случаев это избыточно.

Что предлагает C# вместо этого

Начиная с .NET 3.5 в языке есть Func<T>, Action<T> и лямбда-выражения. Strategy по сути означает «передай поведение как параметр». А в C# функции и так являются объектами первого класса.

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

public class SortContext
{
public void ExecuteSort(List<int> list, Action<List<int>> sortAlgorithm)
{
sortAlgorithm(list);
}
}

// использование
var context = new SortContext();

context.ExecuteSort(numbers, list => list.Sort());
context.ExecuteSort(numbers, list =>
{
// своя логика сортировки
});


Один метод принимает делегат Action<List<int>>. Никаких интерфейсов, никаких отдельных файлов. Поведение передаётся напрямую.

Ещё пример. Допустим, есть расчёт скидки:

public class PriceCalculator
{
public decimal Calculate(decimal price, Func<decimal, decimal> discountStrategy)
{
return discountStrategy(price);
}
}

var calculator = new PriceCalculator();

decimal result = calculator.Calculate(100m, price => price * 0.9m);
decimal vipResult = calculator.Calculate(100m, price => price * 0.8m);


Func<decimal, decimal> принимает цену, возвращает цену со скидкой. Стратегия задаётся в одну строку прямо в месте вызова.

Когда интерфейсы всё-таки нужны

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

public interface IPaymentStrategy
{
bool Validate(PaymentDetails details);
PaymentResult Process(PaymentDetails details);
void Rollback(string transactionId);
}


Здесь три связанных метода. Засунуть их в три отдельных Func можно, но читаемость пострадает. Интерфейс даёт единую точку контракта и нормально тестируется через моки.

Ещё один аргумент за интерфейс: когда реализации регистрируются в DI-контейнере и выбираются в рантайме. Например, разные провайдеры оплаты в зависимости от региона пользователя. Тут интерфейс упрощает конфигурацию и тестирование.

Если стратегия укладывается в одну функцию, используйте Func<T> или Action<T>. Это проще, короче и не требует дополнительных абстракций. Если стратегия включает несколько связанных операций или вам нужна подмена реализаций через DI, интерфейс по-прежнему оправдан. Паттерн не устарел, но применять его стоит осознанно, а не по привычке.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍144
🔥 База по экономике токенов и кэшированию от AI Platform Lead из Bitrix24

Знакомьтесь, Сергей Нотевский. AI Platform Lead в Bitrix24.

Он один из ключевых экспертов нашего курса AgentOps. На своих лекциях он детально разбирает экономику AI-агентов, кэширование токенов, LLM-инфраструктуру и вывод генеративных систем в стабильный прод.

Мы попросили Сергея поделиться материалами для тех, кто хочет оптимизировать косты на LLM в проде. Сохраняйте методичку по prefix cache метрике, которая напрямую влияет на ваши деньги.

Как говорят создатели Manus:
“KV-cache hit rate is the single most important metric for a production-stage AI agent.”


🛠 Что внутри методички (комбо из 3 статей + код):
Экономика кэширования — особенности провайдеров и как правильно считать затраты.

Частые анти-паттерны — почему ваш кэш постоянно сбрасывается и вы платите больше.

Кэш в AI-агентах — специфика работы с памятью в автономных системах.


🍒 Вишенка на торте: готовый SKILL для агента, который делает ревью вашего проекта, находит анти-паттерны и предотвращает низкое попадание в кэш.

Забрать комбо-материалы на GitHub

P.S. Если хотите послушать Сергея вживую — ловите его на конференциях Kode Waves (май), Conversations AI и Highload Spb (июнь).

🎁 Акция в честь старта продаж!

Прямо сейчас при покупке Инженерного трека вы получаете полный доступ к материалам курса «Разработка ИИ-агентов» в подарок.

👉 Забрать 2 курса по цене 1 и начать обучение
1
😶 Vibe hiring: статистика, которая объясняет странные отказы

Найм по интуиции и «культурному фиту» вместо реальных навыков — не редкость, а норма для большой части IT-рынка. И это подтверждается исследованиями, а не только ощущениями кандидатов.
 
➡️ Статья разбирает vibe hiring с цифрами

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта
Please open Telegram to view this post
VIEW IN TELEGRAM
📎 Паттерн Visitor. Почему в C# он больше не нужен

Visitor позволяет добавлять операции к иерархии объектов без изменения их классов. Классический пример — обход AST, дерева документа или иерархии фигур. Вы создаёте отдельный объект-посетитель, который «обходит» структуру и выполняет нужное действие для каждого типа.

Долгое время это был единственный нормальный способ реализовать double dispatch в C#. Но начиная с C# 8 появился switch по типам, а в C# 12 эта возможность стала ещё мощнее. И теперь Visitor выглядит как лишняя обвязка.

Как выглядел Visitor

Допустим, есть иерархия фигур и нужно посчитать площадь.

public interface IShape
{
void Accept(IShapeVisitor visitor);
}

public class Circle : IShape
{
public double Radius { get; init; }
public void Accept(IShapeVisitor visitor) => visitor.Visit(this);
}

public class Rectangle : IShape
{
public double Width { get; init; }
public double Height { get; init; }
public void Accept(IShapeVisitor visitor) => visitor.Visit(this);
}

public interface IShapeVisitor
{
void Visit(Circle circle);
void Visit(Rectangle rectangle);
}

public class AreaCalculator : IShapeVisitor
{
public double Result { get; private set; }

public void Visit(Circle circle) =>
Result = Math.PI * circle.Radius * circle.Radius;

public void Visit(Rectangle rectangle) =>
Result = rectangle.Width * rectangle.Height;
}


Два интерфейса, метод Accept в каждом классе, отдельный класс на каждую операцию. Для двух фигур это терпимо. Для десяти — уже много шаблонного кода.

Как это делается через pattern matching

Тот же расчёт площади в одном switch выражении.

public static double CalculateArea(IShape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
_ => throw new ArgumentException($"Unknown shape: {shape.GetType().Name}")
};


Нет интерфейса IShapeVisitor. Нет метода Accept. Нет отдельного класса калькулятора. Логика читается сверху вниз, в одном месте.

Что происходит, когда добавляется новый тип

Это ключевое отличие. Допустим, вы добавили Triangle.

В Visitor нужно добавить метод Visit(Triangle t) в интерфейс IShapeVisitor, потом реализовать его в каждом посетителе. Если забыли — получите ошибку в рантайме. Или хуже — молчаливое некорректное поведение, если базовый класс имеет реализацию по умолчанию.

В switch выражении компилятор выдаст предупреждение, что Triangle не обработан. Вы узнаете об этом до запуска, на этапе сборки.

// CS8509: The switch expression does not handle all possible values
public static double CalculateArea(IShape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height
// Triangle не обработан — компилятор предупредит
};


Когда Visitor всё ещё уместен

Если иерархия типов находится в чужой библиотеке и вы не можете на неё влиять, а switch по типам становится слишком длинным и разбросанным по кодовой базе — Visitor помогает собрать логику в одном месте. Также он полезен, когда операция требует сложного состояния между вызовами, которое неудобно протаскивать через отдельные методы.

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

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
8🤔3
🆚 Minimal APIs vs контроллер

ASP.NET Core предлагает два способа строить HTTP API. Minimal APIs и контроллеры. Microsoft в документации рекомендует Minimal APIs для новых проектов, потому что это меньше кода, меньше конфигурации и выше производительность. Но это не значит, что контроллеры устарели. Это значит, что нужен осознанный выбор.

Когда Minimal APIs подходят лучше

Minimal APIs хорошо работают, когда API небольшой и сфокусированный. Микросервисы, внутренние API, легковесные эндпоинты, vertical slice архитектура. Каждый эндпоинт явно определяет маршрут, нет лишней церемонии с наследованием и атрибутами.

Вот как выглядит чистый эндпоинт на Minimal API:
public static class CreateOrderEndpoint
{
public static IEndpointRouteBuilder MapCreateOrder(this IEndpointRouteBuilder app)
{
app.MapPost("/orders", async (
CreateOrderRequest request,
ICreateOrderUseCase useCase,
CancellationToken cancellationToken) =>
{
var result = await useCase.ExecuteAsync(request, cancellationToken);
return Results.Created($"/orders/{result.OrderId}", result);
});

return app;
}
}


Читаемо, прямолинейно, бизнес-логика вынесена в ICreateOrderUseCase.

Когда контроллеры всё ещё уместны

Контроллеры полезны там, где уже есть устоявшаяся MVC-инфраструктура, стандартизированные фильтры, сложное версионирование или большая команда, привыкшая к этому паттерну. Если в проекте выстроены конвенции вокруг контроллеров и это работает, переписывать на Minimal APIs ради моды нет смысла.

Что изменилось в .NET 10

В .NET 10 Microsoft добавила поддержку валидации для Minimal APIs, включая кастомизацию ответов через IProblemDetailsService. Раньше отсутствие встроенной валидации было одним из главных аргументов против Minimal APIs. Теперь этот аргумент слабее.

Главная ловушка обоих подходов

Плохо организованный Minimal API проект превращается в огромный Program.cs. Плохо организованный проект на контроллерах превращается в папку с пустыми pass-through экшенами. Ни один из подходов не спасёт от слабой архитектуры.

Что выбрать

Для новых микросервисов и сфокусированных API автор статьи рекомендует Minimal APIs с vertical slices. Но не сваливать всё в Program.cs, а организовывать эндпоинты по фичам, выносить бизнес-логику из обработчиков маршрутов, использовать явные контракты запросов и ответов.

Для систем с уже выстроенной контроллерной архитектурой или жёсткими корпоративными стандартами контроллеры остаются рабочим выбором.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
6🔥2
Что на самом деле запрещает ref struct в C#
 
ref struct появился не просто как ограниченная версия обычного struct. За этим стоит конкретная гарантия компилятора о том, где может находиться такой тип в памяти.
 
Подумайте: что произойдёт, если попробовать передать ref struct в лямбду, сохранить в поле класса или сделать boxing?
 
➡️ Правильный ответ

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#dotnet_challenge
Please open Telegram to view this post
VIEW IN TELEGRAM
😁1
📌 Зачем дата-сайентисту матанализ?

Основная компетенция специалиста по Data Science – способность анализировать и интерпретировать данные, а математика является фундаментом для начала работы.

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

Хотите подготовиться к офферу или подтянуть знания? Оставляйте заявку на наш курс по математике для Data Science 💙

P.S. Только до 31 мая на курс (и вообще на все программы Академии) действует СКИДКА 40%

А как у вас дела с высшей математикой?
❤️ — Помню всё
🔥 — Знаю основы
🌚 — Ничего не знаю

🏃‍♀️ Proglib Academy
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🌚1👾1
📰 Предпоследний майский дайджест

Следующий дайжест будет последним за май.

Microsoft обвинила исследователя в публикации обхода BitLocker через WinRE

Microsoft выложила свежие ISO-образы Windows 11

Образы доступны на странице загрузки Windows Insider Program и подходят для чистой установки, развёртывания на виртуалку или in-place апгрейда.

GitHub взломали через расширение VS Code

Vibe hiring

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#async_news
Please open Telegram to view this post
VIEW IN TELEGRAM
✏️ Service Locator. Почему это антипаттерн, а не альтернатива DI

Service Locator — это глобальный реестр сервисов. Любой класс в любой момент может запросить нужную зависимость через что-то вроде ServiceLocator.Get<IEmailService>(). Выглядит удобно и гибко. На практике — это ловушка, которая усложняет поддержку кода с каждым месяцем.

Марк Симанн назвал этот паттерн антипаттерном ещё в 2010 году. С тех пор аргументы стали только сильнее.

В чём проблема

Когда класс получает зависимости через конструктор, вы видите всё, что ему нужно, прямо в сигнатуре:
public class OrderService
{
private readonly IEmailService _email;
private readonly IOrderRepository _repo;

public OrderService(IEmailService email, IOrderRepository repo)
{
_email = email;
_repo = repo;
}
}


Два параметра — две зависимости. Всё на виду.

Теперь тот же класс через Service Locator:
public class OrderService
{
public void PlaceOrder(Order order)
{
var repo = ServiceLocator.Get<IOrderRepository>();
repo.Save(order);

var email = ServiceLocator.Get<IEmailService>();
email.SendConfirmation(order);
}
}


Снаружи OrderService выглядит так, будто ему ничего не нужно. Зависимости спрятаны внутри метода. Чтобы понять, что класс использует, нужно читать весь его код. На десяти классах это неудобно. На сотне — это археология.

Тесты превращаются в боль

Для юнит-теста с constructor injection вы просто передаёте моки в конструктор.

var service = new OrderService(mockEmail, mockRepo);


С Service Locator нужно сначала настроить глобальный реестр, зарегистрировать в нём все нужные моки, убедиться, что между тестами состояние очищается. Тесты становятся хрупкими и зависимыми друг от друга.

IServiceProvider в бизнес-логике — тот же Service Locator

В .NET есть встроенный DI-контейнер, и соблазн использовать IServiceProvider напрямую велик. Но если вы инжектите IServiceProvider в бизнес-класс и достаёте из него сервисы вручную, это ровно тот же Service Locator, просто в обёртке от Microsoft:
// Так делать не стоит
public class ReportGenerator
{
private readonly IServiceProvider _provider;

public ReportGenerator(IServiceProvider provider)
{
_provider = provider;
}

public void Generate()
{
var formatter = _provider.GetRequiredService<IReportFormatter>();
// ...
}
}


Зависимость от IReportFormatter снова спрятана. Конструктор говорит только «мне нужен весь контейнер», что бесполезно.

Правильный вариант:
public class ReportGenerator
{
private readonly IReportFormatter _formatter;

public ReportGenerator(IReportFormatter formatter)
{
_formatter = formatter;
}

public void Generate()
{
// _formatter уже здесь
}
}


Когда IServiceProvider допустим

Есть ограниченный список ситуаций, где обращение к IServiceProvider оправдано. Фабрики, которые создают объекты с разным временем жизни. Плагинные системы, где набор сервисов неизвестен на этапе компиляции. Инфраструктурный код фреймворка, middleware, активаторы. Во всех этих случаях речь идёт об инфраструктуре, а не о бизнес-логике.

Если ваш класс решает доменную задачу и при этом тянет IServiceProvider, стоит вынести конкретную зависимость в конструктор. Код станет прозрачнее, тесты проще, а рефакторинг перестанет быть раскопками.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
7👍4❤‍🔥2
До 31 мая можно забрать любой курс Proglib Academy со скидкой 40%

Если давно хотели прокачаться в Python, ML, алгоритмах или AI-агентах, сейчас самое время выбрать программу и начать обучение по сниженной цене.

🎁 Разработка AI-агентов от 49.000 ₽ (вместо 69.000 ₽)

Практический курс по разработке AI-агентов для автоматизации задач, работы и собственных проектов

🎁 Курс AgentOps129.000 ₽ (вместо 149.000 ₽)

Для разработчиков и LLM-инженеров, которые хотят внедрять AI-логику в бэкенд и сохранять стабильность сервиса.

🎁 Математика для разработки AI-моделей 23.990 ₽ (вместо 31.990 ₽)

Практическая база по математике для анализа данных, ML и дальнейшего развития в AI.

🎁 Математика для Data Scienceот 29.990 ₽ (вместо 39.990 ₽)

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

🎁 ML для старта в Data Science28.990 ₽ (вместо 38.990 ₽)

Разберётесь в машинном обучении: от базовых понятий и линейных моделей до ансамблей, бустинга и рекомендательных систем.

🎁 Основы IT для непрограммистов16.990 ₽ (вместо 28.990 ₽)

Курс для IT-рекрутеров, маркетологов, проджектов, продактов и всех, кто работает с IT, но не пишет код.

🎁 Архитектуры и шаблоны проектирования27.990 ₽ (вместо 37.900 ₽)

Освоите основные паттерны проектирования и прокачаете навыки архитектора программного обеспечения.

🎁 Специалист по ИИ89.000 ₽ (вместо 113.900 ₽)

Курс для тех, кто хочет получить профессию в сфере ИИ, собрать портфолио из 5 проектов и научиться разрабатывать сложных AI-агентов.

🎁 Алгоритмы и структуры данных 33.990 ₽ (вместо 57.990 ₽)

Подготовитесь к алгоритмическим собеседованиям, разберёте структуры данных и научитесь писать более эффективный код.

🎁 Программирование на языке Python27.990 ₽ (вместо 47.390 ₽)

Освоите Python на практике: без сухой теории, с пошаговой прокачкой навыков и итоговым проектом в портфолио.

🙌 Выбирайте курс по ссылке, оставляйте заявку, и менеджер поможет подобрать программу под ваши цели — https://clc.to/Jeob-A
1
💡 C# 16 переосмысляет unsafe: контракты вместо контекста

В C# переделывают модель работы с unsafe. Ключевое слово больше не будет просто «включать указатели». Оно станет контрактом между вызываемым и вызывающим методом. Превью появится в .NET 11, продакшен-релиз в .NET 12.

Проблема

Сейчас unsafe на методе означает «здесь можно использовать указатели». Он не накладывает обязательств на вызывающую сторону. Метод вроде Marshal.ReadByte принимает IntPtr, разыменовывает указатель внутри, но вызывается из безопасного кода без ограничений. Конвенции не проверяет компилятор и не видит ревьюер с первого взгляда.

Что меняется

unsafe в сигнатуре метода теперь означает контракт: вызывающая сторона обязана выполнить задокументированные условия. Каждая небезопасная операция должна быть обёрнута во внутренний блок unsafe { }. Каждый unsafe-метод должен содержать блок /// <safety> с описанием обязательств.

Вот Marshal.ReadByte в новой модели:
/// <safety>
/// Сумма ptr и ofs должна указывать на байт,
/// доступный вызывающей стороне для чтения.
/// </safety>
public static unsafe byte ReadByte(IntPtr ptr, int ofs)
{
byte* addr = (byte*)ptr;
unsafe
{
// SAFETY: полагаемся на обязательство вызывающей стороны.
return addr[ofs];
}
}


Приведение (byte*)ptr безопасно, это преобразование числа. А вот addr[ofs] опасно, потому что разыменовывает указатель. Именно эта строка обёрнута в unsafe { }.

Другие изменения

unsafe на типе теперь вызывает ошибку компиляции. Область действия опускается на уровень методов и свойств. unsafe на методе больше не создаёт unsafe-контекст автоматически. Типы указателей в сигнатуре больше не распространяют unsafe, опасна только операция разыменования. Новое ключевое слово safe обязательно для extern-деклараций, пропустить оба модификатора нельзя.

Включение

Модель включается через новое свойство проекта и работает независимо от <AllowUnsafeBlocks>. Для миграции планируется dotnet format fixer, который механически обернёт вызовы в unsafe { } и перенесёт модификаторы с типов на методы. Писать <safety>-документацию придётся вручную.

Нарушения становятся ошибками компиляции, не предупреждениями. Модель повторяет подход Rust и Swift, но с учётом экосистемы .NET. Для тех, кто использует unsafe, код станет прозрачнее для ревью.

➡️ Блог разработчиков

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍1
🧑‍💻 Генераторы в .NET замедляют билд

Генераторы кода работают внутри компилятора. Они запускаются при каждой сборке и при каждом нажатии клавиши в IDE. Если не следить за ними, билд на 10 секунд может превратиться в полторы минуты без видимых причин. Вот что можно сделать, чтобы этого не допустить.

Сделайте сгенерированный код видимым

По умолчанию сгенерированные .g.cs файлы не записываются на диск. Они существуют только в памяти компилятора. Когда что-то ломается, вы видите ошибку в файле, который не можете открыть.

Добавьте в Directory.Build.props в корне решения:
<Project>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>
$(BaseIntermediateOutputPath)generated
</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>

Теперь все сгенерированные файлы попадают в папку obj/generated. Их можно читать, искать по ним и ставить точки останова в отладчике. Это настройка на один раз, и она стоит того.

Замеряйте время через binary log

Ощущение «билд стал медленнее» ничего не даёт. Нужны конкретные числа по каждому генератору. Соберите проект с флагом:
dotnet build -bl

Откройте полученный msbuild.binlog в MSBuild Structured Log Viewer. Найдите задачу CSC. Внутри будет список всех генераторов с временем выполнения в миллисекундах. Собирайте именно полное решение, а не отдельный проект. Стоимость генератора умножается на количество проектов, которые его подтягивают.

Проверьте транзитивные зависимости

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

В том же binary log задача CSC показывает все загруженные сборки анализаторов и генераторов. Пройдитесь по списку. Если видите незнакомое имя, выясните, откуда оно пришло. Команда dotnet nuget why поможет отследить цепочку зависимостей.

Используйте только инкрементальные генераторы

Если вы пишете свои генераторы, реализуйте IIncrementalGenerator, а не устаревший ISourceGenerator. Инкрементальный генератор кеширует промежуточные результаты. Если входные данные не изменились, он пропускает повторную обработку.

Но сам по себе интерфейс ничего не гарантирует. Генератор, который реализует IIncrementalGenerator и при этом пересобирает всё заново на каждый вызов, работает так же медленно, как устаревший. Кеширование должно быть реальным. Пайплайн нужно строить так, чтобы неизменённые входы давали кешированный выход.

Принимайте решения по числам

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

Главное тут не гадать, а смотреть в binary log. Там есть имя генератора и его стоимость в миллисекундах. Этого достаточно, чтобы принять осознанное решение.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤‍🔥3👾1
📎 NuGet Package Pruning в .NET 10: меньше шума в зависимостях

Если вы запускали NuGet Audit на .NET-проекте, то наверняка видели предупреждения о транзитивных пакетах, которые не устанавливали. Часто это System.Text.Json, System.Memory и подобные, которые уже есть в рантайме в более свежей версии.

Предупреждение есть, а реальной проблемы нет. В .NET 10 эту ситуацию исправили.

В чём была проблема

Многие библиотеки на nuget.org таргетят netstandard2.0 и тянут за собой пакеты вроде System.Text.Json 8.0.0. Проект на .NET 10 уже содержит их в рантайме в более новой версии, но NuGet всё равно резолвит старый пакет в граф. Когда на него публикуется CVE, сканер помечает его как уязвимый.

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

Что такое package pruning

Package pruning убирает из графа зависимостей пакеты, которые уже поставляются .NET Runtime Libraries. NuGet при restore сверяется со списком пакетов, входящих в целевой фреймворк. Если транзитивная зависимость попадает в диапазон, она исключается. Пакет не скачивается и не фигурирует в аудите.

Для прямых PackageReference логика другая: NuGet ставит PrivateAssets='all' и IncludeAssets='none'. Ссылка остаётся в csproj, пока вы сами её не удалите. Если пакет можно убрать полностью, NuGet выдаёт NU1510.

Pruning работает только в пределах версии фреймворка. Проект на net8.0 с транзитивной зависимостью System.Text.Json 9.0.0 не уберёт пакет, потому что платформа поставляет только 8.0.x.

Что изменилось в .NET 10

Package pruning впервые появился как opt-in в SDK 9.0.200. В .NET 10 он включён по умолчанию для net10.0 и выше. Одновременно NuGetAuditMode по умолчанию стал all, то есть NuGet проверяет и транзитивные зависимости.

По телеметрии Microsoft, проекты с новыми дефолтами получают на 70% меньше транзитивных предупреждений об уязвимостях. Restore ускоряется до 50% на уровне проекта за счёт меньшего графа.

Дефолтные настройки .NET 10:
<PropertyGroup>
<NuGetAuditMode>all</NuGetAuditMode>
<RestoreEnablePackagePruning>true</RestoreEnablePackagePruning>
</PropertyGroup>


Граф зависимостей теперь точнее отражает то, что приложение реально использует. Меньше ложных срабатываний, быстрее restore, понятнее аудит. Для проектов на .NET 10 всё работает из коробки.

➡️ Блог разработчиков

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🎉4🔥2👏1