Паттерн Iterator позволяет обходить коллекцию, не раскрывая её внутреннюю структуру. Классическая реализация на C# требовала ручного написания
IEnumerator<T> с методами MoveNext(), Reset() и свойством Current.Это выглядело громоздко. Даже для простого обхода приходилось писать отдельный класс, хранить состояние, следить за позицией. Десятки строк кода ради того, чтобы пройтись по элементам.
Как это выглядело раньше:
public class NumberCollection : IEnumerable<int>
{
private readonly int[] _items = { 1, 2, 3, 4, 5 };
public IEnumerator<int> GetEnumerator()
{
return new NumberEnumerator(_items);
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class NumberEnumerator : IEnumerator<int>
{
private readonly int[] _items;
private int _position = -1;
public NumberEnumerator(int[] items)
{
_items = items;
}
public int Current => _items[_position];
object IEnumerator.Current => Current;
public bool MoveNext()
{
_position++;
return _position < _items.Length;
}
public void Reset() => _position = -1;
public void Dispose() { }
}
Отдельный класс, ручное управление индексом, реализация
IDisposable. И всё это ради последовательного перебора массива.Что изменил `yield return`
Теперь компилятор умеет генерировать всю эту машину состояний самостоятельно. Вы пишете
yield return, а компилятор создаёт класс с MoveNext(), Current и корректным управлением состоянием за вас.Тот же результат в несколько строк:
public static IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
yield return 4;
yield return 5;
}
Обход ленивый. Каждый элемент вычисляется только при запросе. Это важно, когда вы работаете с большими или бесконечными последовательностями:
public static IEnumerable<int> EvenNumbers()
{
int n = 0;
while (true)
{
yield return n;
n += 2;
}
}
// Берём первые 10 чётных чисел
var result = EvenNumbers().Take(10).ToList();
Попробуйте реализовать бесконечную последовательность через ручной
IEnumerator. Код будет работать, но читать его будет заметно сложнее.Связка с LINQ
yield return хорошо сочетается с LINQ. Можно строить цепочки обработки, где каждый шаг выполняется лениво:public static IEnumerable<string> ProcessUsers(IEnumerable<User> users)
{
foreach (var user in users)
{
if (user.IsActive)
yield return user.Name.ToUpper();
}
}
// Или через LINQ
var names = users
.Where(u => u.IsActive)
.Select(u => u.Name.ToUpper());
Оба варианта ленивые. Ни один не создаёт промежуточных коллекций, пока вы не вызовете
ToList(), foreach или другой терминальный оператор.Когда ручной итератор всё ещё нужен
Есть случаи, где
yield return не подойдёт. Например, если нужен асинхронный обход с отменой, сложная логика очистки ресурсов при досрочном прерывании, или обход нетривиальных структур вроде графов с контролем состояния обхода. В таких ситуациях ручная реализация IEnumerator<T> или IAsyncEnumerator<T> оправдана.Но для большинства задач
yield return закрывает вопрос полностью. Компилятор C# решил эту задачу ещё в 2005 году. Если вы пишете ручной итератор для простого обхода коллекции, стоит проверить, не делаете ли вы работу, которую компилятор готов взять на себя.📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍8
C# Разработчик — удалёнка.
.NET-разработчик (Senior) — удалёнка.
Разработчик C# (.NET) — тоже удалёнка.
Please open Telegram to view this post
VIEW IN TELEGRAM
Стратегия инкапсулирует семейство алгоритмов и позволяет подменять их на лету. Звучит полезно. На практике это часто превращается в десятки файлов ради замены одной функции. В современном 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
👍14❤4
🔥 База по экономике токенов и кэшированию от AI Platform Lead из Bitrix24
Знакомьтесь, Сергей Нотевский. AI Platform Lead в Bitrix24.
Он один из ключевых экспертов нашего курса AgentOps. На своих лекциях он детально разбирает экономику AI-агентов, кэширование токенов, LLM-инфраструктуру и вывод генеративных систем в стабильный прод.
Мы попросили Сергея поделиться материалами для тех, кто хочет оптимизировать косты на LLM в проде. Сохраняйте методичку по prefix cache метрике, которая напрямую влияет на ваши деньги.
Как говорят создатели Manus:
🛠 Что внутри методички (комбо из 3 статей + код):
🍒 Вишенка на торте: готовый SKILL для агента, который делает ревью вашего проекта, находит анти-паттерны и предотвращает низкое попадание в кэш.
— Забрать комбо-материалы на GitHub
P.S. Если хотите послушать Сергея вживую — ловите его на конференциях Kode Waves (май), Conversations AI и Highload Spb (июнь).
🎁 Акция в честь старта продаж!
Прямо сейчас при покупке Инженерного трека вы получаете полный доступ к материалам курса «Разработка ИИ-агентов» в подарок.
👉 Забрать 2 курса по цене 1 и начать обучение
Знакомьтесь, Сергей Нотевский. 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
Найм по интуиции и «культурному фиту» вместо реальных навыков — не редкость, а норма для большой части IT-рынка. И это подтверждается исследованиями, а не только ощущениями кандидатов.
📍 Навигация: Вакансии • Задачи • Собесы
Please open Telegram to view this post
VIEW IN TELEGRAM
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:
Читаемо, прямолинейно, бизнес-логика вынесена в
Когда контроллеры всё ещё уместны
Контроллеры полезны там, где уже есть устоявшаяся MVC-инфраструктура, стандартизированные фильтры, сложное версионирование или большая команда, привыкшая к этому паттерну. Если в проекте выстроены конвенции вокруг контроллеров и это работает, переписывать на Minimal APIs ради моды нет смысла.
Что изменилось в .NET 10
В .NET 10 Microsoft добавила поддержку валидации для Minimal APIs, включая кастомизацию ответов через
Главная ловушка обоих подходов
Плохо организованный Minimal API проект превращается в огромный
Что выбрать
Для новых микросервисов и сфокусированных API автор статьи рекомендует Minimal APIs с vertical slices. Но не сваливать всё в
Для систем с уже выстроенной контроллерной архитектурой или жёсткими корпоративными стандартами контроллеры остаются рабочим выбором.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view
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 мая на курс (и вообще на все программы Академии) действует
А как у вас дела с высшей математикой?
❤️ — Помню всё
🔥 — Знаю основы
🌚 — Ничего не знаю
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 — это глобальный реестр сервисов. Любой класс в любой момент может запросить нужную зависимость через что-то вроде
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-агентов для автоматизации задач, работы и собственных проектов
🎁 Курс AgentOps — 129.000 ₽ (вместо 149.000 ₽)
Для разработчиков и LLM-инженеров, которые хотят внедрять AI-логику в бэкенд и сохранять стабильность сервиса.
🎁 Математика для разработки AI-моделей — 23.990 ₽ (вместо 31.990 ₽)
Практическая база по математике для анализа данных, ML и дальнейшего развития в AI.
🎁 Математика для Data Science — от 29.990 ₽ (вместо 39.990 ₽)
Курс для тех, кто хочет решать задачи, которые дают на собеседованиях на позицию дата-сайентиста в бигтехе.
🎁 ML для старта в Data Science — 28.990 ₽ (вместо 38.990 ₽)
Разберётесь в машинном обучении: от базовых понятий и линейных моделей до ансамблей, бустинга и рекомендательных систем.
🎁 Основы IT для непрограммистов — 16.990 ₽ (вместо 28.990 ₽)
Курс для IT-рекрутеров, маркетологов, проджектов, продактов и всех, кто работает с IT, но не пишет код.
🎁 Архитектуры и шаблоны проектирования — 27.990 ₽ (вместо 37.900 ₽)
Освоите основные паттерны проектирования и прокачаете навыки архитектора программного обеспечения.
🎁 Специалист по ИИ — 89.000 ₽ (вместо 113.900 ₽)
Курс для тех, кто хочет получить профессию в сфере ИИ, собрать портфолио из 5 проектов и научиться разрабатывать сложных AI-агентов.
🎁 Алгоритмы и структуры данных — 33.990 ₽ (вместо 57.990 ₽)
Подготовитесь к алгоритмическим собеседованиям, разберёте структуры данных и научитесь писать более эффективный код.
🎁 Программирование на языке Python — 27.990 ₽ (вместо 47.390 ₽)
Освоите Python на практике: без сухой теории, с пошаговой прокачкой навыков и итоговым проектом в портфолио.
🙌 Выбирайте курс по ссылке, оставляйте заявку, и менеджер поможет подобрать программу под ваши цели — https://clc.to/Jeob-A
Если давно хотели прокачаться в Python, ML, алгоритмах или AI-агентах, сейчас самое время выбрать программу и начать обучение по сниженной цене.
🎁 Разработка AI-агентов — от 49.000 ₽ (вместо 69.000 ₽)
Практический курс по разработке AI-агентов для автоматизации задач, работы и собственных проектов
🎁 Курс AgentOps — 129.000 ₽ (вместо 149.000 ₽)
Для разработчиков и LLM-инженеров, которые хотят внедрять AI-логику в бэкенд и сохранять стабильность сервиса.
🎁 Математика для разработки AI-моделей — 23.990 ₽ (вместо 31.990 ₽)
Практическая база по математике для анализа данных, ML и дальнейшего развития в AI.
🎁 Математика для Data Science — от 29.990 ₽ (вместо 39.990 ₽)
Курс для тех, кто хочет решать задачи, которые дают на собеседованиях на позицию дата-сайентиста в бигтехе.
🎁 ML для старта в Data Science — 28.990 ₽ (вместо 38.990 ₽)
Разберётесь в машинном обучении: от базовых понятий и линейных моделей до ансамблей, бустинга и рекомендательных систем.
🎁 Основы IT для непрограммистов — 16.990 ₽ (вместо 28.990 ₽)
Курс для IT-рекрутеров, маркетологов, проджектов, продактов и всех, кто работает с IT, но не пишет код.
🎁 Архитектуры и шаблоны проектирования — 27.990 ₽ (вместо 37.900 ₽)
Освоите основные паттерны проектирования и прокачаете навыки архитектора программного обеспечения.
🎁 Специалист по ИИ — 89.000 ₽ (вместо 113.900 ₽)
Курс для тех, кто хочет получить профессию в сфере ИИ, собрать портфолио из 5 проектов и научиться разрабатывать сложных AI-агентов.
🎁 Алгоритмы и структуры данных — 33.990 ₽ (вместо 57.990 ₽)
Подготовитесь к алгоритмическим собеседованиям, разберёте структуры данных и научитесь писать более эффективный код.
🎁 Программирование на языке Python — 27.990 ₽ (вместо 47.390 ₽)
Освоите Python на практике: без сухой теории, с пошаговой прокачкой навыков и итоговым проектом в портфолио.
🙌 Выбирайте курс по ссылке, оставляйте заявку, и менеджер поможет подобрать программу под ваши цели — https://clc.to/Jeob-A
❤1
В 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 секунд может превратиться в полторы минуты без видимых причин. Вот что можно сделать, чтобы этого не допустить.
Сделайте сгенерированный код видимым
По умолчанию сгенерированные
Добавьте в
Теперь все сгенерированные файлы попадают в папку
Замеряйте время через binary log
Ощущение «билд стал медленнее» ничего не даёт. Нужны конкретные числа по каждому генератору. Соберите проект с флагом:
Откройте полученный
Проверьте транзитивные зависимости
Генераторы кода приезжают вместе с NuGet-пакетами. Вы подключаете библиотеку логирования или маппер, а внутри пакета лежит генератор, который теперь запускается в каждом проекте. Вы его не выбирали, но он работает.
В том же binary log задача CSC показывает все загруженные сборки анализаторов и генераторов. Пройдитесь по списку. Если видите незнакомое имя, выясните, откуда оно пришло. Команда
Используйте только инкрементальные генераторы
Если вы пишете свои генераторы, реализуйте
Но сам по себе интерфейс ничего не гарантирует. Генератор, который реализует
Принимайте решения по числам
Если дорогой генератор пришёл из стороннего пакета, вы не можете его исправить. Но можете решить, стоит ли пакет своего времени сборки. Иногда стоит. Иногда дешевле отказаться от пакета и решить задачу иначе.
Главное тут не гадать, а смотреть в binary log. Там есть имя генератора и его стоимость в миллисекундах. Этого достаточно, чтобы принять осознанное решение.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view
Генераторы кода работают внутри компилятора. Они запускаются при каждой сборке и при каждом нажатии клавиши в 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