C# Portal | Программирование
13.9K subscribers
1.15K photos
126 videos
29 files
930 links
Присоединяйтесь к нашему каналу и погрузитесь в мир для C#-разработчика

Сотрудничество, реклама: @devmangx

Менеджер: @Spiral_Yuri

РКН: https://clck.ru/3FocB6
Download Telegram
Паттерн Стратегия представляет шаблон проектирования, который определяет набор алгоритмов, инкапсулирует каждый из них и обеспечивает их взаимозаменяемость. В зависимости от ситуации мы можем легко заменить один используемый алгоритм другим. При этом замена алгоритма происходит независимо от объекта, который использует данный алгоритм.

Когда использовать стратегию?


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

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

Когда необходимо менять поведение объектов на стадии выполнения программы

Когда класс, применяющий определенную функциональность, ничего не должен знать о ее реализации

Формальное определение паттерна на языке C# может выглядеть следующим образом:

public interface IStrategy
{
void Algorithm();
}

public class ConcreteStrategy1 : IStrategy
{
public void Algorithm()
{}
}

public class ConcreteStrategy2 : IStrategy
{
public void Algorithm()
{}
}

public class Context
{
public IStrategy ContextStrategy { get; set; }

public Context(IStrategy _strategy)
{
ContextStrategy = _strategy;
}

public void ExecuteAlgorithm()
{
ContextStrategy.Algorithm();
}
}


Интерфейс IStrategy, который определяет метод Algorithm(). Это общий интерфейс для всех реализующих его алгоритмов. Вместо интерфейса здесь также можно было бы использовать абстрактный класс.

Классы ConcreteStrategy1 и ConcreteStrategy, которые реализуют интерфейс IStrategy, предоставляя свою версию метода Algorithm(). Подобных классов-реализаций может быть множество.

Класс Context хранит ссылку на объект IStrategy и связан с интерфейсом IStrategy отношением агрегации.

В данном случае объект IStrategy заключена в свойстве ContextStrategy, хотя также для нее можно было бы определить приватную переменную, а для динамической установки использовать специальный метод.

Теперь рассмотрим конкретный пример. Существуют различные легковые машины, которые используют разные источники энергии: электричество, бензин, газ и так далее. Есть гибридные автомобили. В целом они похожи и отличаются преимущественно видом источника энергии. Не говоря уже о том, что мы можем изменить применяемый источник энергии, модифицировав автомобиль. И в данном случае вполне можно применить паттерн стратегию:

class Program
{
static void Main(string[] args)
{
Car auto = new Car(4, "Volvo", new PetrolMove());
auto.Move();
auto.Movable = new ElectricMove();
auto.Move();

Console.ReadLine();
}
}
interface IMovable
{
void Move();
}

class PetrolMove : IMovable
{
public void Move()
{
Console.WriteLine("Перемещение на бензине");
}
}

class ElectricMove : IMovable
{
public void Move()
{
Console.WriteLine("Перемещение на электричестве");
}
}
class Car
{
protected int passengers; // кол-во пассажиров
protected string model; // модель автомобиля

public Car(int num, string model, IMovable mov)
{
this.passengers = num;
this.model = model;
Movable = mov;
}
public IMovable Movable { private get; set; }
public void Move()
{
Movable.Move();
}
}


В данном случае в качестве IStrategy выступает интерфейс IMovable, определяющий метод Move(). А реализующий этот интерфейс семейство алгоритмов представлено классами ElectricMove и PetroleMove. И данные алгоритмы использует класс Car.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍92
Я вижу эту ошибку почти в каждом кодбейсе, который ревьюю.

Разрабы делают async-вызовы строго по одному:

var user = await GetUserAsync();
var orders = await GetOrdersAsync();
var stats = await GetStatsAsync();


Каждый await ждет завершения перед тем, как стартанет следующий.
3 вызова по 200 мс каждый = 600 мс суммарно.

Надо освоить Task.WhenAll().

var userTask = GetUserAsync();
var ordersTask = GetOrdersAsync();
var statsTask = GetStatsAsync();

await Task.WhenAll(userTask, ordersTask, statsTask);


Теперь общее ожидание = время самой медленной задачи. 600 мс превращаются примерно в 200 мс.

Я использую Task.WhenAll(), когда у меня:

* несколько API-запросов, которые не зависят друг от друга
* сбор данных для дашборда из разных источников
* отправка нотификаций в несколько каналов (email, SMS, push)
* инвалидация кэша по нескольким ключам

Почему мне это нравится:

* никаких доп. потоков, просто более умное планирование
* async I/O ждет ответы и не блокирует
* одно простое изменение дает огромный прирост по перформансу

* работает только если задачи реально независимы
* если Task B зависит от результата Task A, параллелить нельзя
* если задач тысячи, я добавляю SemaphoreSlim для ограничения параллелизма


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍245
Самый быстрый способ перестать ненавидеть регулярки: https://regex101.com/

Собирай, тестируй и дебажь regex пошагово, с объяснениями.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥86👍4
Операторы break и continue с метками в C# 👀

Сейчас это champion proposal


outer: for (int x = 0; x < xMax; x++)
{
for (int y = 0; y < yMax; y++)
{
if (ShouldSkipRest(x, y))
continue outer;

if (ShouldExitAll(x, y))
break outer;
}
}


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯22👏6👍1
Одна строка кода для ускорения EF Core

Когда EF Core загружает связанные сущности через Include(), он генерирует один огромный SQL-запрос с множеством JOIN. Результат? Тысячи дублирующихся строк, гигабайты трафика и медленная работа.

Пример:
var posts = await context.Posts
.Include(p => p.Comments)
.ThenInclude(c => c.Reactions)
.ToListAsync();


При 100 постах, у каждого по 10 комментариев, в каждом по 10 реакций — вместо 11 100 записей получаем результирующий набор на 10 000 строк.

Добавьте .AsSplitQuery() — и EF Core разобьёт один большой запрос на несколько маленьких:
var posts = await context.Posts
.AsSplitQuery() // ← магия здесь
.Include(p => p.Comments)
.ThenInclude(c => c.Reactions)
.ToListAsync();


Query Splitting это не хак и не костыль. Это правильный способ работы с EF Core в read-heavy сценариях.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🤯62🔥1😁1
Публиковать доменные события до или после завершения транзакции?

По сути ты выбираешь между немедленной консистентностью и eventual consistency.

Я всегда публикую доменные события после коммита транзакции, желательно через Outbox.

А ты бы что выбрал и почему?

С EF interceptors можно творить реально интересные штуки.

Публикация доменных событий это всего лишь один из кейсов.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
В .NET 10 JIT умеет убирать аллокацию из-за boxing при итерации по интерфейсам.

Но вот что еще круче: если GetEnumerator реализован через yield return, JIT в некоторых случаях тоже может это оптимизировать!

Классический пример: перебор RepeatedField<T>

public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < this.count; ++i)
yield return this.array[i];
}


Бенчмарк

private readonly RepeatedField<int> _repeatedField 
= GenerateData();

private static RepeatedField<int> GenerateData()
{
var result = new RepeatedField<int>();
result.AddRange(Enumerable.Range(1, 100));
return result;
}

[Benchmark]
public int Foreach_Over_RepeatedField()
{
int result = 0;
foreach (var x:int in _repeatedField)
{
result += x;
}

return result;
}


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
10🔥3
.NET Reverse Proxy в 3 строки кода.

Этого достаточно, чтобы поднять YARP. Понятно, что настройки прокси нужно отдельно задать через appsettings.

Но все равно впечатляет, насколько легко быстро получить рабочий прокси.

Пробовал уже YARP?

Балансировка в YARP так же проста: по сути, просто обновляешь proxy settings.

Вот что нужно, чтобы стартануть

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍42🤨1
В .NET 11 Preview 2 вмержили фичу из C# 15: аргументы для collection expressions.

Теперь можно задавать аргументы создания прямо в выражении коллекции:

List<string> names = [with(capacity: 10), ... elements ...];

Spec: тут

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👎12🤯6👍52
Совет по C#:

if (x == null) { x = new List<string>(); }

x ??= new List<string>();

Результат тот же, но в одну строку.

??= присваивает значение только если слева null.

Доступно начиная с C# 8.0.

Если вы предпочитате первый подход, это не значит что вы плохой разработчик 😜

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
21👍12😁4🥴2
Группировка данных

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

Он строит ILookup<TKey, TElement> это по сути готовая, неизменяемая коллекция групп. В каждой группе лежат элементы с одинаковым ключом.

var users = new[]
{
new { Name = "Anna", Department = "Dev" },
new { Name = "Boris", Department = "Dev" },
new { Name = "Clara", Department = "QA" }
};

var lookup = users.ToLookup(u => u.Department);

foreach (var user in lookup["Dev"])
{
Console.WriteLine(user.Name); // Anna, Boris
}


Достаёшь группу через индексатор lookup["Dev"]. Если такого ключа нет, вернётся пустая последовательность (без исключения).

ToLookup vs GroupBy:

GroupBy возвращает IEnumerable<IGrouping<TKey, TElement>> и работает с отложенным выполнением. То есть группировка реально происходит при переборе, и при повторном переборе может считаться заново.

ToLookup делает всё сразу и держит результат в памяти:

➡️сгруппировал один раз при вызове
➡️потом дергаешь группы сколько угодно, ничего не пересчитывается
➡️но память под всю структуру выделяется сразу

Когда ToLookup заходит лучше всего

Используй, если нужно:

➡️много раз обращаться к одним и тем же группам
➡️ не ловить лишние пересчёты группировки
➡️получать пустую выборку вместо исключений при неизвестном ключе
➡️иметь неизменяемую структуру

Не лучший выбор, если:

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

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
6👍6
Забавный трюк для .NET: как в рантайме узнать, в каком файле и на какой строке объявлен метод

Почему не подходят [CallerMemberName]/[CallerFilePath]/[CallerLineNumber]?
Потому что они работают только на месте вызова и требуют менять сигнатуры методов. А если тебе нужно взять локацию *любого* MethodInfo (например, для снапшот-тестов или логов) — это уже мимо.

Идея: Используем Portable PDB: в PDB лежит маппинг IL → исходники (файл, строки, колонки) через sequence points.

Настройка

Обычно portable PDB генерятся по умолчанию, но можно явно:

▪️отдельным файлом:

<PropertyGroup>
<DebugType>portable</DebugType>
</PropertyGroup>


▪️или вшить в сборку:

<PropertyGroup>
<DebugType>embedded</DebugType>
</PropertyGroup>


Реализация (суть)

Читаем PE, ищем embedded pdb, если нет — пытаемся открыть .pdb рядом. Потом по MetadataToken метода достаем MethodDebugInformation, берем первый sequence point и вытаскиваем Document.Name (путь к файлу) + строку/колонку.

Минимальный скелет:

// NuGet: System.Reflection.Metadata (9.*)

public static (string FilePath, SequencePoint SequencePoint)? GetMethodLocation(this MethodInfo methodInfo)
{
var location = methodInfo.DeclaringType?.Assembly.Location;
if (string.IsNullOrEmpty(location)) return null;

using var fs = File.OpenRead(location);
using var reader = new PEReader(fs);

var pdbProvider = reader.ReadDebugDirectory()
.Where(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb)
.Select(e => reader.ReadEmbeddedPortablePdbDebugDirectoryData(e))
.FirstOrDefault();

try
{
if (pdbProvider is null &&
!reader.TryOpenAssociatedPortablePdb(location, File.OpenRead, out pdbProvider, out _))
return null;

var pdbReader = pdbProvider!.GetMetadataReader();

var methodHandle = MetadataTokens.MethodDefinitionHandle(methodInfo.MetadataToken);
var mdi = pdbReader.GetMethodDebugInformation(methodHandle);

if (mdi.SequencePointsBlob.IsNil) return null;

var sp = mdi.GetSequencePoints().FirstOrDefault();
if (sp.Document.IsNil) return null;

var doc = pdbReader.GetDocument(sp.Document);
var filePath = pdbReader.GetString(doc.Name);

return (filePath, sp);
}
finally
{
pdbProvider?.Dispose();
}
}


Пример использования:

var method = typeof(Program).GetMethod(nameof(SampleMethod))!;
var loc = method.GetMethodLocation();

Console.WriteLine($"{loc?.FilePath}:{loc?.SequencePoint.StartLine}");


Важный нюанс: в проде PDB могут быть не задеплоены специально (безопасность/размер/политики), так что всегда держим null-кейсы и не рассчитываем на 100% наличие.

Источник

@KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍61
Расширяющий метод Entity Framework вместо TagWith.

public static class QueryableExtensions
{
public static IQueryable<T> TagWithDebugInfo<T>(
this IQueryable<T> query,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
var debugInfo = $"Caller: {memberName}, File: {filePath}, Line: {lineNumber}";

// также можно добавить другую информацию, которую вы хотите, например, userId и т. д.

return query.TagWith(debugInfo); //delegate to built in TagWith
}
}

//usage sample
var query = context.YourDbSet
.Where(x => x.SomeProperty == someValue)
.TagWithDebugInfo();


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8👏3🔥2
Пожалуйста, пересмотрите решение и всё-таки разрешите асинхронную валидацию моделей 👀

Сейчас это висит в майлстоуне “.NET 11 Planning”, очень хотелось бы, чтобы фичу реально довели до реализации.

@KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
8💯5😐4❤‍🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Проектировать схемы БД становится сильно проще

Это бесплатный дизайнер схем, который работает на 100% локально, даже офлайн. Можно собирать как простые, так и сложные схемы, и ничего не улетает в облако.

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

https://relatedb.com/

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
18🔥3
OpenTelemetry + .NET: трейсы, метрики и логи в SigNoz

В статье показано развёртывание через docker-compose, генерация быстрых/медленных/ошибочных запросов и просмотр коррелированных трейсов, метрик и логов в SigNoz. Описаны настройки .NET (ResourceBuilder, ActivitySource, авто‑инструментирование, OTLP) и практические советы; исходники в репо.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
Хватит дергать DateTime.UtcNow прямо в бизнес-логике.

Строчка выглядит безобидно, но именно из-за нее тесты потом превращаются в кошмар

Вот типичная ловушка:

* DateTime.UtcNow > user.SubscriptionEnd

Вроде ок, да?

Но как только пытаешься это протестировать, вылезает проблема:

- ты не контролируешь DateTime.UtcNow
- тесты становятся флаки или обрастают костылями
- бизнес-правила оказываются привязаны к инфраструктуре

Есть варианты лучше:

1. Передавать DateTime параметром. Самый простой путь, отлично заходит для маленьких методов.

2. Вынести время в интерфейс IClock. Масштабируется лучше и дружит с тестами.

Когда ты инвертируешь зависимость от времени, доменная логика становится:

проще тестировать
более детерминированной
с нормальным разделением ответственности

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

“Мне нужно будет это тестировать?”

Если да, выноси часы наружу.

@KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🥴9🔥31👍1
Не пиши в SQL = NULL или != NULL

Для NULL всегда используй IS NULL / IS NOT NULL

NULL это “нет значения / неизвестно”, и обычные операторы сравнения (=, !=, <, >) с ним не работают, потому что NULL не равен, не больше и не меньше вообще ничего.

= / != сравнивают значения. А NULL это не значение, а отсутствие значения. Поэтому IS / IS NOT проверяют именно наличие или отсутствие значения.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍23🔥3🥴3
This media is not supported in your browser
VIEW IN TELEGRAM
File Explorer в Visual Studio теперь поддерживает поиск прямо из Solution Explorer.

@KodBlog
👍132🔥2🥴2
Лучший конфиг для HttpClient это тот, который ты не копируешь в каждом сервисе.

В .NET ты настраиваешь все один раз через IHttpClientFactory (и Aspire, если используешь):

* service discovery для вызовов сервис-сервис
* дефолтную resiliency-настройку, чтобы кратковременные фейлы не ломали тебе жизнь

Дальше просто инжектишь клиент и пишешь бизнес-код.

- полный разбор

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👏6
25 Claude Code skills для .NET-разрабов

Покрывает штуки, которые реально важны и неочевидны (типа: как тестировать транзакционные письма в ASP.NET Core?) и вообще помогает улучшать вывод LLM (типы!).

github.com/Aaronontheweb/dotnet-skills?tab=readme-ov-file

Все это про то, чтобы Claude делал вещи нормально без того, чтобы ты на него орал.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
6👍2🥴2