StepOne | Степан Минин – Telegram
StepOne | Степан Минин
3.42K subscribers
247 photos
35 videos
6 files
312 links
StepOne by Степан Минин @ststphn

Твой первый шаг к успеху в программировании

Закрытый тг канал https://news.1rj.ru/str/tribute/app?startapp=slOA

По вопросам рекламы @Spiral_Yuri

Ютуб https://www.youtube.com/@steponeit
Download Telegram
StepOne | Степан Минин
Как в C# 11 появилась обобщённая математика 7 ноября при поддержке CUSTIS состоится MskDotNet Meetup #55, где я выступлю с докладом. В рамках доклада рассмотрим с нуля концепцию обобщённой математики, как она выглядит в C# 11, как могла выглядеть в предыдущих…
unmanaged constraint

Позавчера на докладе меня попросили прокомментировать этот фрагмент кода из ML NET.

В частности слушателя заинтересовало, что означает ограничение unmanaged.

Тогда ответить не вышло, честно не знал 😅

Однако я не я, если не разберусь в теме, и не напишу об этом пост 😁

Запоминаем - where T : unmanaged означает, что тип T это что-то из списка ниже:

⭐️ sbyte, byte, short, ushort, int, uint, long, ulong, nint, nuint, char, float, double, decimal, bool

🌴 любой enum тип

🎁 любой тип указателя (например void*)

👽 любой пользовательский struct с полями только из unmanaged типов

Иными словами такое ограничение запрещает типу иметь внутри себя ссылки, управляемые GC, то есть это не ссылочный тип, или тип без членов ссылочного типа на любом уровне вложенности.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍462🔥2
Лучший совет по интернет безопасности в 2023 году
😁33💯7🔥2
Чуть больше двух лет назад я проходил собеседование в OZON, где в рамках технического этапа нужно было провести ревью кода.

Я не знал, что они так проверяли исключительно знание примитивов синхронизации и многопоточности в .NET.

И начал набрасывать, как плохо, не по ООП, спроектирован код, заявленным DDD не пахнет вообще и домен вдобавок смоделирован некорректно, а ещё принципы SOLID нарушаются.

Естественно, сказать, что тогда не прошёл то собеседование - не сказать ничего.

И вот алгоритмы ютуба решили напомнить мне о неудачном опыте, предложив видео паренька, вышедшее 29 октября этого года.

Конечно, разбирает правильно, правда немного неуверенно и нечётко, но почему за два года задача не изменилась совсем?

Буквально, код повторяется посимвольно.

Почему так, для меня загадка 🤷‍♂️
👍11😁6🤯1
ActionFilterAttribute


Это очень крутая штука, которую надо знать всем C# специалистам, разрабатывающим сервисы с помощью ASP NET Core.

Есть обычные фильтры, предоставляющие механизм построения пайплайна вокруг запроса (действия до и после, выполнение сквозного функционала и так далее).

Но простые операции для отдельных ручек можно реализовывать прямо в атрибутах, чтобы не тратить ресурсы на вычисление эндпоинта с помощью рефлексии.

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

[AttributeUsage(validOn: AttributeTargets.Method)]
public class HeaderRequiredAttribute : ActionFilterAttribute
{
private readonly string _headerName;

public HeaderRequiredAttribute(string headerName) =>
_headerName = headerName;

public override void OnActionExecuting(ActionExecutingContext context)
{
var httpContext = context.HttpContext;
var headerValue = httpContext.Request.Headers[_headerName].ToString();
if (string.IsNullOrWhiteSpace(headerValue))
throw new MissingRequiredHeaderException(_headerName);
}
}
👍14🤯4🔥3🤩1
Лайфхак для айтишников 🔥
😁30🍌4🤩2🌭2
Task vs ValueTask

Что такое Task знают все C# разработчики - класс, олицетворяющий длительную операцию в асинхронном программировании.

А вот в 7 версии языка появилась структура ValueTask, и до сих пор не всем ясно её назначение.

Из интересного в ней два поля - первое для хранения задачи, второе для хранения результата (при наличии).

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

ValueTask это структура, а значит вероятнее всего окажется на стеке, и расходов на высвобождение памяти будет потрачено существенно меньше.

Соответственно, стоит использовать эту структуру, когда мы ожидаем, что задача будет выполняться синхронно большинство времени (например, закэшированный результат) или расходы на вызов и ожидание обычной задачи будут больше.

Однако, есть ряд ограничений, то есть, нельзя делать следующие вещи:

⭐️ Ожидать задачу конкурентно или несколько раз

⭐️ Вызывать метод AsTask несколько раз

⭐️ Использовать .Result или .GetAwaiter().GetResult(), если задача не завершена

⭐️ Использовать задачу в комбинаторах WhenAll() и WhenAny()
Please open Telegram to view this post
VIEW IN TELEGRAM
👍193👏3
StepOne | Степан Минин
Внедрение конкретной реализации в стандартном контейнере Допустим, у нас есть некоторый интерфейс, который имеет несколько реализаций: public interface IDependency {} public class DependencyImplOne : IDependency {} public class DependencyImplTwo : IDependency…
ASP NET 8 наконец-то добавит многообразие зависимостей 🥳

Реализовано это будет через ключи (например строковые).

Возвращаясь к старому примеру, он преобразится следующим образом:

public interface IDependency {}

public class DependencyImplOne : IDependency {}
public class DependencyImplTwo : IDependency {}

builder.Services.AddKeyedSingleton<IDependency, DependencyImplOne>("one");
builder.Services.AddKeyedSingleton<IDependency, DependencyImplTwo>("two");


Далее, использовать вот так, с помощью атрибута [FromKeyedServices]:

public class BarService : IBarService
{
public BarService([FromKeyedServices("one")] IDependency dependency) // DependencyImplOne
{
}
}

public class BazService : IBazService
{
public BazService([FromKeyedServices("two")] IDependency dependency) // DependencyImplTwo
{
}
}


Для меня, как поклонника ООП, это знаковая веха в развитии платформы 🔥

Считаю, что ради этой киллер фичи можно смело планировать переезд на новый LTS релиз!
🔥24👍8🤯4😱2
Почему я всегда сообщаю рекрутеру зарплатные ожидания? 🤔

Есть распространённое мнение о зарплатных переговорах: «кто первый назвал цифру, тот и проиграл».

Однако это не совсем так.

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

Во-вторых, разработчики это такие же рядовые сотрудники, как и другой персонал: для них существует вполне конкретный ФОТ.

То есть торги - это инструмент не растяжения мешка с деньгами, а прощупывания его границ.

И вот однажды я собеседовался в дом рф, попробовав всеми правдами и неправдами скрыть цифру 💵

Изворачивался и уходил от ответа так, как никогда в жизни 🤯

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

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

Сказать, что чувствовал себя тогда идиотом - не сказать ничего 😁

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

А терять время не хочет никто.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21🔥4💯4
StepOne | Степан Минин
Немного о pet project В прошлом посте я написал про то, как опыт коммерческой разработки повлиял на мой стиль его ведения. Однако, может возникнуть закономерный вопрос: «Так чем ты там занимаешься?» Отвечаю. Сейчас мой основной open source contribute -…
Правильность архитектуры проверяется при внедрении нового функционала

Кажется, написал что-то очевидное, но столкнулся с этим лично на своём pet project.

В рамках него я пишу интерпретатор TypeScript подобного языка.

Этот проект родился, как дипломная работа в бакалавриате бауманки.

Но поскольку сроки были сильно сжаты, то изначально всё сделано на скорую руку.

Год назад хотел добавить в язык аналог выражения with из C#, но столкнулся с тем, что из-за спешки сделать это будет больно.

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

Подсветить то, насколько важно перейти на паттерн Visitor, помогла моя попытка внедрить DDD и выделить домен системы.

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

При этом стали видны новые точки роста - ошибки в реализации «Посетителя», изоляция домена, изменение знания одних частей системы о других и так далее.
👍10🔥4👏21
В EF Core 8 узаконили Value Object 🤩

Одно из тех обновлений, которое не следует пропускать 😱

Например, есть следующая сущность заказа Order вместе с сущностью клиента Customer:

public class Customer
{
public int Id { get; set; }
public required string Name { get; set; }
public required Address Address { get; set; }
public List<Order> Orders { get; } = new();
}

public class Order
{
public int Id { get; set; }
public required string Contents { get; set; }
public required Address ShippingAddress { get; set; }
public required Address BillingAddress { get; set; }
public Customer Customer { get; set; } = null!;
}


Кажется, что уже существует семантика работы с типами значениями, ведь никто не отменял атрибут [Owned].

Однако, при попытке сохранить сущность, которая использует один инстанс на несколько полей возникнет ошибка:

var address = GetAddress();

customer.Orders.Add(
new Order { Contents = "Tesco Tasty Treats", BillingAddress = address, ShippingAddress = address, });

await context.SaveChangesAsync();


Кажется, что всё будет хорошо, но получим исключение InvalidOperationException с сообщением

«Cannot save instance of 'Order.ShippingAddress#Address' because it is an owned entity without any reference to its owner. Owned entities can only be saved as part of an aggregate also including the owner entity.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.PrepareToSave()»

Теперь, достаточно разметить тип данных атрибутом [ComplexType].
При этом он может быть иммутабельным!

[ComplexType]
public record Address(string Line1, string? Line2, string City, string Country, string PostCode);


Соответственно, такое обновление сущности:

customer.Address = customer.Address with { Line1 = "Peacock Lodge" };

await context.SaveChangesAsync();


Сгенерирует следующий SQL:

UPDATE [Customers] SET [Address_Line1] = @p0
OUTPUT 1
WHERE [Id] = @p1;


То есть, это собственный тип на стероидах 💉
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍2🎉1🤣1🏆1
partial взрывает мне мозг 🤯

Недавно решил изучить такую возможность C#, как объявление типов данных в разных файлах с помощью ключевого слова partial.

В коммерческой практике не так часто это встречалось.

И я обнаружил, что с этой фичей можно делать просто супер странные вещи.

Например, у нас есть простенький интерфейс IConract:

public interface IContract
{
void Foo();
}


Сделаем раздробленный класс MyPartialClass:

public partial class MyPartialClass
{
// ...
}


И оказывается, что реализацию интерфейса этим классом можно написать вообще в другом файле, и если иметь перед глазами только первый, то об этом и не догадаешься!

public partial class MyPartialClass : IContract
{
public virtual void Foo()
{
throw new NotImplementedException();
}
}


При этом виртуальный метод Foo можно спокойно переопределять в той же манере в наследниках:

public partial class MyPartialClassDerived : MyPartialClass
{
public override void Foo()
{
Console.WriteLine(this);
base.Foo();
}
}


Опять же, догадаться о существовании подобных членов типа будет достаточно проблематично.

Злоупотреблять таким в командной работе конечно же не рекомендую.

Однако, подобный функционал будет полезен разработчикам решений на основе Source Generators.

Таким образом, можно докидывать компилятором различные бойлерплейты, которые не хочется писать руками.
💯9🤯3🙏2
StepOne | Степан Минин
Отлаживать код в Rider смогут даже младенцы 👶 JetBrains продолжают развивать лучшую IDE для C# разработчиков и удивлять их новыми обновлениями. В грядущей версии 2023.3 завезут, на мой взгляд, настоящую killer feature, которая называется Debugger Data Flow…
Релизнулся Rider 2023.3 😱

Завезли много всего интересного: поддержку .NET 8 SDK, одновременный запуск нескольких проектов, предиктивный дебаггинг, вытащили из превью AI ассистента и многое другое

Например, добавили отрисовку диаграм зависимостей типов 🎁

Теперь можно воочию увидеть архитектуру своего проекта, и убедиться, что её нужно переделать 😱

Короче говоря, побежали обновляться! 🏃‍♂️
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17😁31🤯1
Лучшая статья за прошедшую неделю 👍

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

"Вопрос: Может ли монолит быть также эффективен как микросервисная архитектура?

Ответ: Нет, ему никогда не стать настолько же медленным
" 👏

https://habr.com/ru/articles/779362/
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9😁8👏1💯1
Тут на днях разнесли в пух и прах первичные конструкторы в C# 12

В целом я согласен со многими поинтами, и поэтому хочется посмотреть, так сказать, bigger picture.

Подумайте: есть ли у вас такое ощущение, что последней крупной и продуманной до конца фичей была добавленная в C# 5 асинхронность?

Все последующие обновления чувствуются минорными - их спокойно можно было бы нумеровать 5.1, 5.2, 5.3 и так далее.

Даже обобщённую математику не доделали до конца!

Скорее всего, проблема в том, что Андерс Хейлсберг не занимается проектом, зато о TypeScript теперь кричат из каждого утюга 🤔

https://habr.com/ru/articles/779166/
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8💯51🙏1
Контравариантность и ковариантность 🧠

В одном из профильных чатов видел сообщение о том, что упомянутая в заголовке тема даётся чуть ли не сложнее всего, что входит в основы C#.

Иногда, кажется проще запомнить наглядно, на примерах.

Также, стоит упомянуть, что всё нижеописанное актуально именно в дженериках.

Контравариантность ⬆️

Возможность подставить более базовый тип на место формального типового параметра, которая используется для объявления аргументов функций:

interface IFooIn<in TInputType>
{
void Bar(TInputType input);
}


Пример из стандартной библиотеки - IComparable<>, где благодаря контравариантности можно сделать так:

IComparable<IEnumerable<char>> charEnumerableComparable = // ...;

// ...

IComparable<string> stringComparable = charEnumerableComparable;


Ковариантность ⬇️

Возможность подставить более производный тип на место формального типового параметра, которая используется для объявления возврата значений из функций:

interface IFooOut<out TOutputType>
{
TOutputType Baz();
}


Пример из стандартной библиотеки - IEnumerable<>, где благодаря ковариантности можно сделать так:

IEnumerable<Task<object>> tasksWithResults = // ...;

// ...

IEnumerable<Task> tasks = tasksWithResults;


Дополнительно 🤓

Во-первых, первое и второе можно совмещать в рамках одного типа:

interface IFooInOut<in TInputType, out TOutputType>
{
TOutputType Foo(TInputType input);
}


Во-вторых, это очень мощный инструмент на стыке параметрического полиморфизма и полиморфизма подтипов, который позволяет строить мощные расширяемые и универсальные системы типов.

В-третьих, самый смак этой фичи в том, что кастинг, который был показан выше, в силу своей неявности по перфомансу равен отсутствию кастинга, поскольку в результирующем IL отсутствуют инструкции castclass.
🤯9🔥7👍52
StepOne | Степан Минин
Правильность архитектуры проверяется при внедрении нового функционала Кажется, написал что-то очевидное, но столкнулся с этим лично на своём pet project. В рамках него я пишу интерпретатор TypeScript подобного языка. Этот проект родился, как дипломная работа…
Visitor NET. Уникальный проект

Когда я начал рефакторинг своего интерпретатора, передо мной встал вопрос, как реализовывать паттерн Visitor.

Его нужно было применить несколько раз, поскольку задач по работе с AST немало: многоэтапный статический анализ, вывод типов, кодогенерация и так далее.

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

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

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

Изначально, хотелось сделать ациклический вариант шаблона - он был гибким в том плане, что конечный посетитель строился из "кубиков" - возможностей посещать какие-либо элементы:

public interface IVisitor<in TVisitable, out T> : IVisitor
where TVisitable : IVisitable
{
T Visit(TVisitable visitable);
}


Соответственно сам визитор выглядел примерно так:

public class TypeSystemLoader :
IVisitor<ScriptBody>,
IVisitor<AbstractSyntaxTreeNode>,
IVisitor<TypeDeclaration>
{
// ...
}


Сам посещаемый элемент должен уметь вызвать visitor.Visit(this) и для этого ему нужно знать, что у типа есть правильная перегрузка метода.

Классический ациклический вариант предлагает следующую реализацию метода Accept:

public class ScriptBody : AbstractSyntaxTreeNode
{
public override void Accept(IVisitor visitor)
{
if (visitor is IVisitor<ScriptBody> typed)
typed.Visit(this);
}
}


Однако, прибегать к явному даункастингу в такой ситуации очень не хочется, потому что это лишает код полиморфности.

Попытка провалилась - циклическую зависимость всё-таки пришлось оставить, поскольку во избежание явного кастинга посещаемому элементу приходилось знать о конкретном посетителе:

public interface IVisitable<in TVisitor, out T> : IVisitable
where TVisitor : IVisitor
{
T Accept(TVisitor visitor);
}


Такая реализация нарушала OCP и LSP, поскольку в посещаемом элементе отсутствовала единая точка входа для любого посетителя, независимая от результата обработки.

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

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

Всего этого удалось добиться с помощью контравариантности и паттерна CRTP:

public interface IVisitor<in TVisitable, out TReturn>
where TVisitable : IVisitable<TVisitable>
{
TReturn Visit(TVisitable visitable);
}

public interface IVisitable<out TVisitable>
where TVisitable : IVisitable<TVisitable>
{
TReturn Accept<TReturn>(IVisitor<TVisitable, TReturn> visitor);
}


Тогда, в самом посещаемом элементе приходится писать два метода Accept: первый для корня иерархии, который будет вызывать посетитель; а второй уже для того, чтобы под капотом вызвался нужный Visit:

public record Operation(
char Symbol,
BinaryTreeNode Left,
BinaryTreeNode Right) : BinaryTreeNode, IVisitable<Operation>
{
public override TReturn Accept<TReturn>(
IVisitor<BinaryTreeNode, TReturn> visitor) =>
Accept(visitor);

public TReturn Accept<TReturn>(
IVisitor<Operation, TReturn> visitor) =>
visitor.Visit(this);
}


С написанием этих двух методов можно не заморачиваться, так как я создал incremental source generator, который автоматизирует написание кода.

Таким образом, удалось решить нетривиальную задачу по созданию ациклической, универсальной, расширяемой и типобезопасной реализации шаблона Visitor без применения явного даункастинга.

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

Поэтому, давайте поддержим мой проект звездой на гитхаб ⭐️
https://github.com/Stepami/visitor-net

Нейросеть так не сможет.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥16👍72🕊2
Как вы заработали свою первую 1000 рублей в IT?

Первые деньги мне принёс заказ на создание Telegram бота на Python!
Anonymous Poll
11%
Оплачиваемая стажировка
48%
Работа
6%
Репетиторство / наставничество / менторство
20%
Фриланс
11%
Всё ещё нищук (
4%
Другое (пишите в комментариях)
🤔2👍1🤯1😱1🙏1🐳1🌭1
Что означает «CAP» в CAP теореме?

1️⃣ Consistency, Availability, Partition Tolerance

2️⃣ Control, Allocation, Process

3️⃣ Code, Architecture, Pipeline

4️⃣ Connection Access Protocol

Правильный ответ: 1️⃣

Пояснение:

CAP теорема утверждает, что в любой распределённой системе можно добиться только 2 из 3 гарантий.

Consistency - данные между узлами системы согласованы и не противоречат друг другу

Availability - любой request к системе завершается response

Partition Tolerance - выход из строя отдельных узлов системы не влияет на её общую работоспособность


Ставь 🐳 если тебя это спрашивали на собесе
Please open Telegram to view this post
VIEW IN TELEGRAM
🐳24👍13😱41👌1