Завезли превью версию C# 12
Только недавно говорили о том, что пока у Microsoft мало чего есть, что можно было бы показать.
Впрочем, так и оказалось)
Новых фич всего три:
1️⃣ Первичные конструкторы для
2️⃣ Полноценный type alias
3️⃣ Значения по умолчанию для параметров в лямбдах
В целом ничего groundbreaking 🤷♂️
Только недавно говорили о том, что пока у Microsoft мало чего есть, что можно было бы показать.
Впрочем, так и оказалось)
Новых фич всего три:
1️⃣ Первичные конструкторы для
class и struct2️⃣ Полноценный type alias
3️⃣ Значения по умолчанию для параметров в лямбдах
В целом ничего groundbreaking 🤷♂️
Microsoft News
Check out new C# 12 preview features!
The first set of C# 12 features are here in preview including primary constructors, using aliases, and lambda expression parameters.
👍3🤔1👌1
Как изменить таймаут для конкретного запроса в
Одной из лучших практик по работе с
Однако, возникают ситуации, когда для разных запросов требуется разное поведение клиента. Например, разные таймауты.
Проблема в том, что
Но я бы не писал этот пост, если бы не существовало решения проблемы. А решение довольно простое:
Такое решение можно не только использовать "в лоб", но и обернуть в пайплайн из
1️⃣ Пользовательский таймаут меньше того, что установлен в
2️⃣ Пользовательский таймаут валиден. Проще говоря, время ожидания больше 0 секунд.
HttpClient?Одной из лучших практик по работе с
HttpClient в C# считается переиспользование одного экземпляра клиента для множества запросов. Как минимум во избежание port exhaustion.Однако, возникают ситуации, когда для разных запросов требуется разное поведение клиента. Например, разные таймауты.
Проблема в том, что
HttpClient.Timeout устанавливается единожды, во время создания клиента. И несмотря на наличие public set'тера, это значение не может быть изменено впоследствии. Любые попытки пресекаются выбрасыванием InvalidOperationException.Но я бы не писал этот пост, если бы не существовало решения проблемы. А решение довольно простое:
TimeSpan timeout = GetMyTimeout();
using (var tokenSource = new CancellationTokenSource(timeout))
{
var response = await httpClient.GetAsync(uri, tokenSource.Token);
HandleResponse(response);
}
Такое решение можно не только использовать "в лоб", но и обернуть в пайплайн из
DelegatingHandler'ов. Для того чтобы оно работало, потребуется убедиться в двух вещах:1️⃣ Пользовательский таймаут меньше того, что установлен в
HttpClient.Timeout2️⃣ Пользовательский таймаут валиден. Проще говоря, время ожидания больше 0 секунд.
👏8👍4🔥2🤩1
.NET не идеален
Производительность приложения в современном мире очень важный аспект, которым нельзя пренебрегать при разработке.
Это влияет и на пользовательский опыт, и на финансовые показатели продукта, его конкурентоспособность, репутацию.
Но даже проверенные временем платформы разработки не могут гарантировать ожидаемого поведения на 100% при соблюдении всех правил.
Например, Евгений Пешков в своём докладе рассказывает о неэффективности реализации базовых примитивов синхронизации асинхронных задач.
Оказывается, они содержат фатальный недостаток: используют внутри синхронные блокировки.
Также, автор доклада ведёт канал @epeshkblog, где можно прочитать про готовящееся им решение проблемы и множество других интересных аспектов платформы, о которых вы даже не догадывались.
Производительность приложения в современном мире очень важный аспект, которым нельзя пренебрегать при разработке.
Это влияет и на пользовательский опыт, и на финансовые показатели продукта, его конкурентоспособность, репутацию.
Но даже проверенные временем платформы разработки не могут гарантировать ожидаемого поведения на 100% при соблюдении всех правил.
Например, Евгений Пешков в своём докладе рассказывает о неэффективности реализации базовых примитивов синхронизации асинхронных задач.
Оказывается, они содержат фатальный недостаток: используют внутри синхронные блокировки.
Также, автор доклада ведёт канал @epeshkblog, где можно прочитать про готовящееся им решение проблемы и множество других интересных аспектов платформы, о которых вы даже не догадывались.
Telegram
.NET epeshk blog
Канал с заметками о C# и .NET
Поддержать канал: https://news.1rj.ru/str/blog_donate/2
Обратная связь: https://forms.gle/3uRz7FmzUA26Kw4y5
Поддержать канал: https://news.1rj.ru/str/blog_donate/2
Обратная связь: https://forms.gle/3uRz7FmzUA26Kw4y5
👍6🔥1👏1
Обобщай это, обобщай то
В новой статье вновь аккуратно стёр границы между абстрактной алгеброй и объектно-ориентированным программированием.
А ещё подробно зарылся внутрь
Жду вас на хабре с плюсом!
#хабр
В новой статье вновь аккуратно стёр границы между абстрактной алгеброй и объектно-ориентированным программированием.
А ещё подробно зарылся внутрь
System.Numerics и Generic Math.Жду вас на хабре с плюсом!
#хабр
🔥7👍3🤩1
Почему C#?
Думаю, читая этот канал или статьи на хабре, внимательный подписчик мог заметить, что я являюсь C# разработчиком.
Помимо общих материалов про IT делюсь решениями некоторых кейсов, и примеры кода всегда привожу именно на этом языке.
Конечно, это не единственный язык, который я знаю, причём хорошо. Но в своё время меня привлекла концепция C#.
В далёкие времена его позиционировали как работу над ошибками Java. И если вдаться в детали, то это соответствует действительности на все 100%. Сейчас, уже Java берёт многое от своего последователя.
В общем, если рассматривать шарпы не как ЯП, а как продукт, то у него крутое честное и продающее позиционирование.
Поделитесь в комментариях историей своего выбора ЯП
Думаю, читая этот канал или статьи на хабре, внимательный подписчик мог заметить, что я являюсь C# разработчиком.
Помимо общих материалов про IT делюсь решениями некоторых кейсов, и примеры кода всегда привожу именно на этом языке.
Конечно, это не единственный язык, который я знаю, причём хорошо. Но в своё время меня привлекла концепция C#.
В далёкие времена его позиционировали как работу над ошибками Java. И если вдаться в детали, то это соответствует действительности на все 100%. Сейчас, уже Java берёт многое от своего последователя.
В общем, если рассматривать шарпы не как ЯП, а как продукт, то у него крутое честное и продающее позиционирование.
Поделитесь в комментариях историей своего выбора ЯП
👍9👏2
Пока начинаются майские праздники, на хабре вышла публикация про IDE языка C#, выполняющая запросы LINQ для таблиц Excel.
Отличная статья, отличная разработка. Но, хочу обратить внимание, что это перевод, и автор разработки не из РФ. Могут быть нюансы с использованием и поддержкой.
Отличная статья, отличная разработка. Но, хочу обратить внимание, что это перевод, и автор разработки не из РФ. Могут быть нюансы с использованием и поддержкой.
Хабр
C# как замена VBA в Excel
Я довольно много пишу на C#, и это мне нравится. Время от времени мне хочется, чтобы можно было использовать C# внутри других приложений. Думаю, одним из таких приложений, в которых хорошо приживётся...
👍6🤯3🥰1
Многоликая регистрация в стандартном контейнере
Допустим, у нас есть некоторый класс, который реализует более одного интерфейса:
Наша задача зарегистировать экземпляр
Попытавшись совершить регистрацию наивным путём, мы потерпим неудачу, зависимости будут ссылаться на разные экземпляры
Но проблема вполне решаема, достаточно лишь зарегистрировать вначале экземпляр
Допустим, у нас есть некоторый класс, который реализует более одного интерфейса:
public interface IBar {}
public interface IFoo {}
public class FooBar : IFoo, IBar {}
Наша задача зарегистировать экземпляр
FooBar таким образом, чтобы при внедрении зависимостей IBar и IFoo использовался один и тот же объект.Попытавшись совершить регистрацию наивным путём, мы потерпим неудачу, зависимости будут ссылаться на разные экземпляры
FooBar:
services.AddSingleton<IFoo, FooBar>();
services.AddSingleton<IBar, FooBar>();
Но проблема вполне решаема, достаточно лишь зарегистрировать вначале экземпляр
FooBar и затем ссылаться на него:
services.AddSingleton<FooBar>();
services.AddSingleton<IFoo>(x => x.GetRequiredService<FooBar>());
services.AddSingleton<IBar>(x => x.GetRequiredService<FooBar>());
🔥19👍9🤯1
Всем доброе утро!
Я часто прохожу собеседования, на одном из последних встретил интересную задачу.
Решение выложу после 10 неудачных попыток в комментариях.
Надо определить, что будет выведено на экране. Допустим, у изначального потока
Я часто прохожу собеседования, на одном из последних встретил интересную задачу.
Решение выложу после 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👍2❤1⚡1
StepOne | Степан Минин
Всем доброе утро! Я часто прохожу собеседования, на одном из последних встретил интересную задачу. Решение выложу после 10 неудачных попыток в комментариях. Надо определить, что будет выведено на экране. Допустим, у изначального потока id равен 1, а при…
Из-за разных обстоятельств чуть-чуть задержался с выкладыванием решения, но всё же в этом посте расставлю все точки над "i", как и обещал.
Но прежде, хочу выразить огромную радость по поводу того, какие подкованные у меня подписчики. В комментариях было много людей, ответивших правильно, причём сразу.
Затем, там началась интересная дискуссия, а далее вообще прислали уже новую задачу.
Для меня, как автора, работающего с аудиторией это большая победа, спасибо вам!
Итак, как уже было верно подмечено в комментах, программа выведет
Почему ответ именно такой?
Задача проверяет понимание того, в какой момент поток из ThreadPool действительно понадобится в случае
В методе
С
Ну а в случае
Но прежде, хочу выразить огромную радость по поводу того, какие подкованные у меня подписчики. В комментариях было много людей, ответивших правильно, причём сразу.
Затем, там началась интересная дискуссия, а далее вообще прислали уже новую задачу.
Для меня, как автора, работающего с аудиторией это большая победа, спасибо вам!
Итак, как уже было верно подмечено в комментах, программа выведет
"1 1 1 1 1 <любое другое число>". Здесь не подразумевались какие-то дополнительные условия, только конфигурация по умолчанию.Почему ответ именно такой?
Задача проверяет понимание того, в какой момент поток из ThreadPool действительно понадобится в случае
await'а.В методе
GetValue2 всё очевидно, поскольку ключевого слова await не было, соответственно автомат компилятором не сгенерируется, и код метода останется неизменным.С
GetValue1 всё чуть интереснее. Кажется, что произойдут все манипуляции, ведь есть и задачка, и await. Но runtime умнее, чем мы думаем и он понимает, что Task.FromResult, ровно как и Task.CompletedTask, создаёт уже завершенную задачу, которую не требуется ожидать. Соотвественно, смена потока не требуется.Ну а в случае
GetValue3, конечно смена потока произойдёт. Несмотря на элементарность задачи, Task.Run уже возвращает запущенную, но не завершённую.❤10👍1🔥1👏1🤩1
Внедрение конкретной реализации в стандартном контейнере
Допустим, у нас есть некоторый интерфейс, который имеет несколько реализаций:
И мы хотим, используя стандартный DI контейнер .NET Core, внедрить в определённый сервис конкретную реализацию этого контракта.
То есть:
К сожалению, стандартный контейнер не предоставляет встроенных возможностей для решения этой задачи.
Он спроектирован просто и минималистично, чтобы новый функционал было легко добавлять согласно индивидуальным потребностям.
Здесь есть несколько вариантов решения:
1⃣ Создание фабрики
2⃣ Создание Service Delegate
3⃣ Внедрение коллекции зависимостей
4⃣ Явная регистрация, например:
Конечно, указанные решения не идеальные.
В такой ситуации стоит посмотреть в сторону других контейнеров.
Ставьте реакции на этот пост, если хотите, чтобы я о них написал.
Допустим, у нас есть некоторый интерфейс, который имеет несколько реализаций:
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…
Итак, любители внедрения зависимостей! Поскольку пост получил большой отклик, начинаю публиковать про другие контейнеры!
🔥6
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>())
);👍10❤1👏1
Autofac. Именованные сервисы
Контейнер 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 километра вражеского кабеля и, использовав его, установил связь с передовыми наблюдательными пунктами.
Чем обеспечил своевременное открытие огня дивизиону.
Смог бы так я?
Большой вопрос.
Хотя в мирной жизни прадед работал начальником АХО завода «Калибр».
Светлая память, с Праздником!
Эта война и праздник останутся сакральными для русского человека, это глубоко в нас прописано на каком-то неизученном языке программирования человеческой сути.
Я точно также храню память и благодарен бабушкам и дедушкам, ковавшим победу.
Одним из этих людей является мой прадед, Воротников Николай Семёнович, кавалер Ордена Славы 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, но связывание происходит по имени параметра конструктора в потребителе контракта.
Например, у нас есть сервис для отправки сообщений
Какие-то ещё контейнеры уже не вижу смысла рассматривать, поскольку их подходы буду похожи на то, о чём я уже рассказал. Также, через какое-то время по совету подписчика сделаю бенчмарк, а пока переключусь на другую тему.
Контейнер 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
}
Какие-то ещё контейнеры уже не вижу смысла рассматривать, поскольку их подходы буду похожи на то, о чём я уже рассказал. Также, через какое-то время по совету подписчика сделаю бенчмарк, а пока переключусь на другую тему.
👍3❤2👏2🔥1🤩1👌1🕊1💯1
Анекдот, классический как книги дяди Боба:
Заходит однажды тестировщик в бар.
Забегает в бар.
Пролезает в бар.
Танцуя, проникает в бар.
Крадется в бар.
Врывается в бар.
Прыгает в бар.
Заказывает:
кружку пива,
2 кружки пива,
0 кружек пива,
999999999 кружек пива,
ящерицу в стакане,
–1 кружку пива,
qwerty кружек пива.
Первый реальный клиент заходит в бар и спрашивает, где туалет. Бар вспыхивает пламенем, все погибают.
Заходит однажды тестировщик в бар.
Забегает в бар.
Пролезает в бар.
Танцуя, проникает в бар.
Крадется в бар.
Врывается в бар.
Прыгает в бар.
Заказывает:
кружку пива,
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 для решения другой задачи, ранее обозначенной на канале:
Такое случается, когда погружаешься в тему глубоко и потребляешь много контента из разных источников.
Однако, пока я разбирался с контейнером StructureMap, обнаружил забавный факт.
Оказалось, что у него есть API для решения другой задачи, ранее обозначенной на канале:
var container = new Container(x =>
{
x.ForConcreteType<FooBar>().Configure.Singleton();
x.Forward<FooBar, IFoo>();
x.Forward<FooBar, IBar>();
});👍5🤔3👏1🤯1
Если вы хотите получить более глубокие знания в области внедрения зависимостей в .NET, то рекомендую прочитать книгу
Dependency Injection in .NET, Mark Seemann
В ней представлены ключевые паттерны DI на чистом C#, объяснение принципа работы контейнеров и раскрывается вопрос интеграции со стандартными технологиями Microsoft.
Dependency Injection in .NET, Mark Seemann
В ней представлены ключевые паттерны DI на чистом C#, объяснение принципа работы контейнеров и раскрывается вопрос интеграции со стандартными технологиями Microsoft.
👍13🔥4❤2👌1