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

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

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

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

Ютуб https://www.youtube.com/@steponeit
Download Telegram
Всем доброе утро!

Я часто прохожу собеседования, на одном из последних встретил интересную задачу.

Решение выложу после 10 неудачных попыток в комментариях.

Надо определить, что будет выведено на экране. Допустим, у изначального потока id равен 1, а при смене потока ему можно присвоить любой другой (2, 3, 10, 77 и так далее).


public static async Task<int> GetValue1()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId); // ?
return await Task.FromResult(100500);
}

public static async Task<int> GetValue2()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId); // ?
return 500100;
}


public static Task<int> GetValue3()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId); // ?
return Task.Run(() => 500100);
}

static async Task Main(string[] args)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId); // 1
var val1 = await GetValue1();
Console.WriteLine(Thread.CurrentThread.ManagedThreadId); // ?
var val2 = await GetValue2();
Console.WriteLine(Thread.CurrentThread.ManagedThreadId); // ?
var val3 = await GetValue3();
Console.WriteLine(Thread.CurrentThread.ManagedThreadId); // ?
}
🤯6🤔4👍211
StepOne | Степан Минин
Всем доброе утро! Я часто прохожу собеседования, на одном из последних встретил интересную задачу. Решение выложу после 10 неудачных попыток в комментариях. Надо определить, что будет выведено на экране. Допустим, у изначального потока id равен 1, а при…
Из-за разных обстоятельств чуть-чуть задержался с выкладыванием решения, но всё же в этом посте расставлю все точки над "i", как и обещал.

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

Итак, как уже было верно подмечено в комментах, программа выведет "1 1 1 1 1 <любое другое число>". Здесь не подразумевались какие-то дополнительные условия, только конфигурация по умолчанию.

Почему ответ именно такой?
Задача проверяет понимание того, в какой момент поток из ThreadPool действительно понадобится в случае await'а.

В методе GetValue2 всё очевидно, поскольку ключевого слова await не было, соответственно автомат компилятором не сгенерируется, и код метода останется неизменным.

С GetValue1 всё чуть интереснее. Кажется, что произойдут все манипуляции, ведь есть и задачка, и await. Но runtime умнее, чем мы думаем и он понимает, что Task.FromResult, ровно как и Task.CompletedTask, создаёт уже завершенную задачу, которую не требуется ожидать. Соотвественно, смена потока не требуется.

Ну а в случае GetValue3, конечно смена потока произойдёт. Несмотря на элементарность задачи, Task.Run уже возвращает запущенную, но не завершённую.
10👍1🔥1👏1🤩1
Внедрение конкретной реализации в стандартном контейнере

Допустим, у нас есть некоторый интерфейс, который имеет несколько реализаций:


public interface IDependency {}

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


И мы хотим, используя стандартный DI контейнер .NET Core, внедрить в определённый сервис конкретную реализацию этого контракта.
То есть:


public class BarService : IBarService
{
public BarService(IDependency dependency) // DependencyImplOne
{
}
}

public class BazService : IBazService
{
public BazService(IDependency dependency) // DependencyImplTwo
{
}
}


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

Здесь есть несколько вариантов решения:

1⃣ Создание фабрики

2⃣ Создание Service Delegate

3⃣ Внедрение коллекции зависимостей IEnumerable<IDependency> с её последующим перебором

4⃣ Явная регистрация, например: services.AddTransient<IBazService, BazService>(_ => new BazService(new DependencyImplTwo()))

Конечно, указанные решения не идеальные.
В такой ситуации стоит посмотреть в сторону других контейнеров.
Ставьте реакции на этот пост, если хотите, чтобы я о них написал.
🔥32👍8🤩1
StepOne | Степан Минин
Внедрение конкретной реализации в стандартном контейнере Допустим, у нас есть некоторый интерфейс, который имеет несколько реализаций: public interface IDependency {} public class DependencyImplOne : IDependency {} public class DependencyImplTwo : IDependency…
Simple Injector. Условная регистрация

Возможность внедрять конкретную реализацию зависимости согласно определённому контексту в этом контейнере реализована с помощью метода RegisterConditional:

container.RegisterConditional<ILogger, NullLogger>(
c => c.Consumer.ImplementationType ==
typeof(HomeController));

container.RegisterConditional<ILogger, FileLogger>(
c => c.Consumer.ImplementationType ==
typeof(UsersController));

container.RegisterConditional<ILogger, DatabaseLogger>(c => !c.Handled);


Из приведённого примера видно, что условная регистрация позволяет настроить поставку зависимости на основе определения типа потребителя.

То есть, HomeController получит NullLogger, UsersController получит FileLogger, а все остальные потребители ILogger получат DatabaseLogger.
7👍2🔥2
StepOne | Степан Минин
Внедрение конкретной реализации в стандартном контейнере Допустим, у нас есть некоторый интерфейс, который имеет несколько реализаций: public interface IDependency {} public class DependencyImplOne : IDependency {} public class DependencyImplTwo : IDependency…
Castle Windsor. Явное указание зависимостей

Возвращаясь к нашему примеру с логгером, допустим, что у сервиса ILogger есть две реализации: некий стандартный Logger и безопасный SecureLogger, который требуется использовать в некотором сервисе TransactionProcessingEngine.

В контейнере Castle Windsor это можно настроить, используя метод Dependency.OnComponent.
В нём указывается конкретная зависимость, которую требуется внедрить.
Перегрузок метода много, соответственно вариантов это сделать несколько: от именованных зависимостей до явного указания типов.

Самый простой вариант будет выглядеть так:


container.Register(
Component.For<ITransactionProcessingEngine>().ImplementedBy<TransactionProcessingEngine>()
.DependsOn(Dependency.OnComponent<ILogger, SecureLogger>())
);
👍101👏1
Autofac. Именованные сервисы

Контейнер Autofac предоставляет возможность внедрять конкретные зависимости, явно указывая некоторый ключ, который соотносится с желаемой зависимостью.

Например, у нас есть сервис IDisplay, отображающий какие-то произведения искусства IArtwork.
Чтобы указать, что мы хотим внедрить конкретную реализацию MyPainting, можно использовать атрибут KeyFilterAttribute.
По указанному ключу, он проведёт фильтрацию и выберет нужную зависимость.

Пример:


public class ArtDisplay : IDisplay
{
public ArtDisplay([KeyFilter("MyPainting")] IArtwork art) { ... }
}

// ...

var builder = new ContainerBuilder();

builder.RegisterType<MyPainting>().Keyed<IArtwork>("MyPainting");
builder.RegisterType<ArtDisplay>().As<IDisplay>().WithAttributeFiltering();

// ...
var container = builder.Build();
7👍1👏1
С Днём Победы!

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

Я точно также храню память и благодарен бабушкам и дедушкам, ковавшим победу.
Одним из этих людей является мой прадед, Воротников Николай Семёнович, кавалер Ордена Славы 3 степени, Ордена Красной Звезды и двух медалей За Отвагу.

Орден Славы он получил за то, что, выполняя приказ командования 24 декабря 1943 года, под вражеским обстрелом 13 раз устранял порванную связь с наблюдательными пунктами батарей дивизиона.
Дважды подвергаясь автоматному обстрелу, он сумел смотать 3 километра вражеского кабеля и, использовав его, установил связь с передовыми наблюдательными пунктами.
Чем обеспечил своевременное открытие огня дивизиону.

Смог бы так я?
Большой вопрос.
Хотя в мирной жизни прадед работал начальником АХО завода «Калибр».

Светлая память, с Праздником!
26🤡11👍4👏2🕊1🥱1
StepOne | Степан Минин
Внедрение конкретной реализации в стандартном контейнере Допустим, у нас есть некоторый интерфейс, который имеет несколько реализаций: public interface IDependency {} public class DependencyImplOne : IDependency {} public class DependencyImplTwo : IDependency…
StructureMap. Настройка конструктора

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

Например, у нас есть сервис для отправки сообщений IMessageService, который реализуют соответственно SmsService и EmailService. И есть некоторые сценарии, в которых нужно использовать разные имплементации, тогда конфигурация будет выглядеть примерно следующим образом:

var container = new Container(x => {
x.For<FooScenario>().Use<FooScenario>()
.Ctor<IMessageService>("messageService")
.Is<SmsService>();
x.For<BarScenario>().Use<BarScenario>()
.Ctor<IMessageService>("messageService")
.Is<EmailService>();
});

// ...

public class FooScenario
{
public FooScenario(IMessageService messageService) // sms
}

// ...

public class BarScenario
{
public BarScenario(IMessageService messageService) // email
}


Какие-то ещё контейнеры уже не вижу смысла рассматривать, поскольку их подходы буду похожи на то, о чём я уже рассказал. Также, через какое-то время по совету подписчика сделаю бенчмарк, а пока переключусь на другую тему.
👍32👏2🔥1🤩1👌1🕊1💯1
Анекдот, классический как книги дяди Боба:

Заходит однажды тестировщик в бар.
Забегает в бар.
Пролезает в бар.
Танцуя, проникает в бар.
Крадется в бар.
Врывается в бар.
Прыгает в бар.

Заказывает:
кружку пива,
2 кружки пива,
0 кружек пива,
999999999 кружек пива,
ящерицу в стакане,
–1 кружку пива,
qwerty кружек пива.

Первый реальный клиент заходит в бар и спрашивает, где туалет. Бар вспыхивает пламенем, все погибают.
😁30👍3🤩2🔥1
StepOne | Степан Минин
Многоликая регистрация в стандартном контейнере Допустим, у нас есть некоторый класс, который реализует более одного интерфейса: public interface IBar {} public interface IFoo {} public class FooBar : IFoo, IBar {} Наша задача зарегистировать экземпляр…
Пока не получается отойти от темы Dependency Injection на все 100%, как того хотелось бы.

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

Однако, пока я разбирался с контейнером StructureMap, обнаружил забавный факт.
Оказалось, что у него есть API для решения другой задачи, ранее обозначенной на канале:


var container = new Container(x =>
{
x.ForConcreteType<FooBar>().Configure.Singleton();

x.Forward<FooBar, IFoo>();
x.Forward<FooBar, IBar>();
});
👍5🤔3👏1🤯1
Константное время превыше всего!😁
😁27👍3🤩1
Если вы хотите получить более глубокие знания в области внедрения зависимостей в .NET, то рекомендую прочитать книгу

Dependency Injection in .NET, Mark Seemann

В ней представлены ключевые паттерны DI на чистом C#, объяснение принципа работы контейнеров и раскрывается вопрос интеграции со стандартными технологиями Microsoft.
👍13🔥42👌1
В настоящий момент работаю над докладом для одной известной технической конференции.

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

https://youtu.be/f5KeOR-XmVw
🔥132
System vs Newtonsoft

Уже известный факт: коробочный сериализатор платформы .NET обзавёлся поддержкой полиморфной сериализации и десериализации.

Но насколько он быстрее по сравнению с Newtonsoft? Я задался этим вопросом и провёл бенчмарк. Сравнил работу с одним экземпляром и списком из 30000 различных объектов, каждый из которых являлся экземпляром некоторого производного класса.

Конфигурация сериализаторов следующая:

▪️camelCase
▪️pretty форматирование
▪️включение метаинформации о типе (поскольку в System её отключить нельзя, то в Newtonsoft нужно включить)
▪️конвертация enum в string

Результат на фото: платформенное решение быстрее стороннего в ~4 раза в сериализации и в ~3.5 раза в десериализации.
👍17🤯52🔥2
Очень хорошо о том, почему разработка это сложно)
😁19💯9🤣2🔥1🤩1
Какую самую главную ошибку совершают новички в ООП?

Начав изучать ООП, многие знакомятся с его тремя столпами: инкапсуляция, наследование и полиморфизм. Но удаётся ли правильно понять все из них?

Если выполнить поиск определения инкапсуляции в Google, то можно получить примерно следующее:

«Инкапсуляция в программировании — это принцип, согласно которому внутреннее устройство сущностей нужно объединять в специальной «оболочке» и скрывать от вмешательств извне.»

А теперь вспомним, что самыми популярными объектно-ориентированными языками программирования являются Java и C#, где модификаторы доступа (private, public, protected, etc.) это одни из самых используемых ключевых слов. И получается, что начинающие программисты путают сокрытие с инкапсуляцией.

На самом же деле, корректнее привести следующее определение:

«Инкапсуляция – это объединение в рамках одной структуры функций и данных, с которыми эти функции работают.»

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

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

Вот простой пример, чтобы понять разницу:

class Doubler
{
private readonly int value;

public Doubler(int value) => this.value = value;

public int Double() => this.value * 2;
}

//…

public int Double(int value) => value * 2;
👍8🤯3🥴2🔥1😱1💯1
Суммарное количество скачиваний моих библиотек превысило тысячу! Ура! 🥳
🔥13🎉7👍31🙏1🥴1
Почему важно развивать алгоритмическое мышление для того чтобы научиться программировать?

Для начала вспомним, что такое алгоритм.

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

Соответственно, для любого алгоритма характерны следующие особенности:
▪️конечность
▪️определенность
▪️ввод и вывод
▪️эффективность

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

Получается, что навык алгоритмизации даёт логическую основу для умения решать задачи пошагово и эффективно.
В то время, как программирование это лишь инструмент описания алгоритмов на языке понятном исполнителю, то есть, машине.
👍11❤‍🔥9🔥2🥴2🤯1😱1
Вечерний воскресный юмор)
😁26🤣32💔1
Что делает оператор new?

Недавно был сильно удивлён, когда узнал, что многие backend разработчики middle уровня в крупных компаниях не знают, что делает ключевое слово new.

Конечно, верхнеуровневая концепция в рамках ООП понятна. Создаём объект, заносим какие-то параметры, значения присваиваются и получается связка некоторых данных под именем одного типа.

Однако, отсутствует низкоуровневое понимание процесса.
То есть, что делать, если в языке нет этого слова new?

Здесь нам поможет обращение к истокам, а именно языку программирования Си.
На его примере очень полезно изучать концепцию динамической памяти и указателей.
Правда делать надо это осторожно, иначе не избежать часов с valgrind.

Так вот память, требуемая для создания объекта выделяется динамически.
Что это значит?

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

Всю эту информацию можно получить только во время выполнения программы.

Соответственно, в Си для этого были добавлены:

▪️ключевое слово sizeof, определяющее количество байтов, необходимое для хранения типа

▪️функции выделения памяти (аллокации): malloc, calloc, realloc

▪️функция освобождения памяти: free

Пользуясь этими конструкциями можно описать примитивное поведение оператора new.
Например, у нас есть структура точки Point:

struct Point {
float x, y;
};

Если на чём-то вроде Java мы напишем: var p = new Point(), то на Си это будет выглядеть так:

struct Point *p = (struct Point*)malloc(sizeof(struct Point));
// обязательно потом высвобождаем память, ведь GC у нас нет!
free(p);

То есть, мы посчитали, сколько нам надо памяти, выделили её и вернули указатель на адрес, по которому лежит созданный объект.
А уже через него можно заниматься инициализацией, обновлением, и так далее.
🔥8👍52🤔1🤯1