Библиотека шарписта | C#, F#, .NET, ASP.NET
22.3K subscribers
2.59K photos
41 videos
85 files
4.88K links
Все самое полезное для C#-разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/b60af5a4

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Download Telegram
👀 Сортировать все ради одного значения — это дорогая привычка

В продакшене до сих пор встречается LINQ паттерн, где коллекцию сначала сортируют, а потом берут первый элемент:
var youngest = people
.OrderBy(p => p.Age)
.First();


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

Правильнее выразить намерение напрямую через MinBy:
var youngest = people.MinBy(p => p.Age);


MinBy возвращает элемент с минимальным ключом и не требует полной сортировки последовательности. Если в коде встречается OrderBy().First() или OrderByDescending().First(), это повод остановиться и проверить, не ищется ли просто минимум или максимум.

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍33🥱5
📎 Когда хочется коротко, но выходит долго

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

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

Типичный паттерн выглядит так:
var totals = orders
.GroupBy(o => o.CustomerId)
.Select(g => new
{
CustomerId = g.Key,
Total = g.Sum(o => o.Amount)
})
.ToList();


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

Если нужна именно аккумуляция, проще и дешевле сделать один проход и складывать суммы в словарь:
var totals = new Dictionary<int, decimal>();
foreach (var order in orders)
{
if (totals.TryGetValue(order.CustomerId, out var current))
totals[order.CustomerId] = current + order.Amount;
else
totals[order.CustomerId] = order.Amount;
}


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

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍191
🖨 Добавляем картинку в PDF

Разбираемся, как встроить изображение в PDF так, чтобы всё корректно печаталось, с помощью C#.

IronPdf — это библиотека, которая работает по принципу «что видите в HTML, то получите в PDF». Вместо возни с координатами вы просто верстаете страницу как для браузера.

Библиотека платная для коммерческих проектов, но есть пробный период. Для некоммерческих целей можно использовать бесплатно.

Самый простой способ — сгенерировать HTML с тегом <img> и конвертировать в PDF:
using IronPdf;

var renderer = new ChromePdfRenderer();

string html = @"
<html>
<body>
<img src='logo.png' style='width: 200px;' />
<p>Текст под картинкой</p>
</body>
</html>";

var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("document_with_image.pdf");


Если картинка приходит из базы данных или веб-формы, удобнее встроить её прямо в HTML через Data URI:
byte[] imageBytes = File.ReadAllBytes("logo.png");
string base64 = Convert.ToBase64String(imageBytes);

string html = $@"
<html>
<body>
<img src='data:image/png;base64,{base64}' style='width: 200px;' />
<h2>Отчёт за декабрь</h2>
</body>
</html>";

var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("report.pdf");


Если нужно добавить штамп или подпись на готовый документ, IronPdf позволяет работать со слоями:
var existingPdf = PdfDocument.FromFile("contract.pdf");

// Создаём HTML со штампом
string stampHtml = @"
<div style='position: absolute; bottom: 50px; right: 50px;'>
<img src='approved_stamp.png' style='width: 100px; opacity: 0.7;' />
</div>";

var stampPdf = renderer.RenderHtmlAsPdf(stampHtml);

// Накладываем на каждую страницу
foreach (var page in existingPdf.Pages)
{
page.AddBackgroundPdf(stampPdf);
}

existingPdf.SaveAs("contract_approved.pdf");


Частые ошибки

• Картинка не появляется — проверьте путь к файлу. IronPdf ищет относительно рабочей директории приложения. Используйте Path.GetFullPath() для отладки или встраивайте через Base64.

• Размытые изображения — убедитесь, что исходное разрешение картинки достаточное. Для печати нужно минимум 300 DPI. Если растягиваете маленькое изображение через width в CSS, оно будет мыльным.

• Огромный размер файла — IronPdf встраивает оригиналы. Оптимизируйте картинки заранее: сжимайте JPEG до 80-85% качества, для PNG используйте инструменты вроде TinyPNG.

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍145
👨‍💻 Как превратить O(1) в O(n)

Одна из самых неприятных просадок производительности в C# начинается не с LINQ операторов, а с того, что коллекции начинают жить как IEnumerable.

Пример который встречается постоянно:
IEnumerable<Order> orders = GetOrders();
if (orders.Count() > 0)
{
// do something
}


Если внутри реально List или массив, то количество элементов доступно быстро через свойство Count или Length, то есть без прохода по данным.

Но Count() как LINQ метод обязан работать для любого IEnumerable, поэтому в общем случае он перечисляет элементы, пока не посчитает все.

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

А если хочется сохранить быстрый путь для коллекций, можно явно проверить интерфейс с Count:
if (orders is ICollection<Order> collection)
{
if (collection.Count > 0)
{
// fast path
}
}
else if (orders.Any())
{
// fallback
}


Если в профилировщике внезапно появился лишний проход по данным, стоит проверить где коллекция превратилась в IEnumerable и где после этого зовется Count().

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱11👍91🤔1😢1
⚡️ Чистый LINQ, грязные аллокации

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

Where и Select сами по себе ленивые, они не выполняются пока не началось перечисление. Но как только появляется ToArray, начинается реальная работа и выделяется новый массив под результат

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

Пример:
foreach (var batch in batches)
{
var validItems = batch
.Where(IsValid)
.Select(Transform)
.ToArray();

Process(validItems);
}


Ручной цикл выглядит скучнее, но дает контроль над буфером и позволяет переиспользовать память:
var buffer = new List<Result>(batch.Count);

foreach (var item in batch)
{
if (!IsValid(item))
continue;
buffer.Add(Transform(item));
}
Process(CollectionsMarshal.AsSpan(buffer));
buffer.Clear();


CollectionsMarshal.AsSpan дает доступ к внутреннему массиву List как Span, то есть можно передать данные дальше без копирования, но важно не менять List пока Span используется.

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱7👍5👾51
📎 SelectMany это двойной foreach, просто в красивой упаковке

SelectMany проектирует каждый элемент во вложенную последовательность и потом склеивает эти последовательности в одну общую.

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

Но если в задаче появляется управление потоком или условия выхода:
var allItems = orders
.SelectMany(o => o.Items)
.Where(i => i.IsActive)
.ToList();


Цикл часто дает больше ясности и предсказуемости:
var allItems = new List<Item>();

foreach (var order in orders)
{
foreach (var item in order.Items)
{
if (!item.IsActive)
continue;
allItems.Add(item);
}
}


Плюс тут легко вставить break, continue, счетчики, ограничения, и не гадать как они сложатся с отложенным выполнением.

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔9😁1🥱1
👨‍💻 Когда if начинает распухать

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

Типовая схема такая. Есть интерфейс Strategy, есть несколько конкретных стратегий, и есть контекст, который держит ссылку на стратегию и вызывает ее метод, не зная деталей реализации. Это снижает связность и позволяет добавлять новые варианты без переписывания старого кода.

Мини пример на C#:
public interface IDiscountStrategy
{
decimal Apply(decimal total);
}

public sealed class RegularDiscount : IDiscountStrategy
{
public decimal Apply(decimal total) => total;
}

public sealed class VipDiscount : IDiscountStrategy
{
public decimal Apply(decimal total) => total * 0.9m;
}

public sealed class Checkout
{
private readonly IDiscountStrategy _discount;

public Checkout(IDiscountStrategy discount) => _discount = discount;

public decimal TotalWithDiscount(decimal total) => _discount.Apply(total);
}


Если стратегия выбирается по условиям, условие должно выбирать объект, а не ветку кода:
var checkout = serviceProvider.GetRequiredService<Checkout>();
var total = checkout.TotalWithDiscount(100m);


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

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
12
🧑‍💻 Исключения — не для обычного управления потоком

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

Антипаттерн, который всё ещё встречается:
{
var value = dictionary[key];
Process(value);
}
catch (KeyNotFoundException)
{
// ignore
}


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

Правильный подход — явное ветвление:
if (dictionary.TryGetValue(key, out var value))
{
Process(value);
}


Этот код читается понятно, работает быстрее и сохраняет исключения значимыми.

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱96👍5😁2
🛠 Parallel.For — когда циклы работают параллельно

Представьте: у вас есть массив из миллиона элементов, и каждый нужно обработать. Обычный цикл for будет делать это последовательно — один элемент за другим. А что если задействовать все ядра процессора?

Базовое использование:
// Было
for (int i = 0; i < 1000; i++)
{
ProcessImage(images[i]);
}

// Стало
Parallel.For(0, 1000, i =>
{
ProcessImage(images[i]);
});


Вот и всё. Task Parallel Library сам распределит работу по потокам, сам управляет ThreadPool, сам балансирует нагрузку.

Когда это имеет смысл:

+ Обработка изображений, видео
+ Математические вычисления
+ Парсинг больших объёмов данных
+ Криптографические операции


Не подходит:

- Обработка меньше 100 элементов
- Операции с базой данных или API
- Быстрые операции вроде i * 2

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
15🥱4
📎 Преобразование Excel в JSON

Сделаем минимальный код для чтения таблиц для конвертации в JSON.

Подключите NuGet-пакеты FreeSpire.XLS и Newtonsoft.Json. Первая читает .xlsx без зависимостей, вторая формирует JSON.

Код начинается с загрузки файла в поток. Библиотека преобразует лист в DataSet, где строки становятся объектами. Первая строка служит заголовками полей.

Чтение и конвертация данных:
using Spire.Xls;
using Newtonsoft.Json;

Workbook wb = new Workbook();
wb.LoadFromFile("данные.xlsx");
DataTable таблица = wb.Worksheets.ExportDataTable();

JsonSerializerSettings опции = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};

string результат = JsonConvert.SerializeObject(таблица, опции);
File.WriteAllText("выход.json", результат);


Игнорируйте null для компактности. Форматируйте даты единообразно. Тестируйте на реальных данных — библиотека FreeSpire.XLS ограничена объемом, но достаточна для большинства случаев.

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🥱3
🤓 Группировка данных без лишних действий

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

Как работает ToLookup

Метод создаёт структуру данных 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 эффективнее

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
14👍8
👨‍💻 ToLookup для поиска дубликатов

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

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

Стандартный подход через GroupBy:
public static IEnumerable<int> FindDuplicates_GroupBy(List<int> data)
{
return data.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key);
}


Работает, но GroupBy выполняется лениво — при каждой итерации происходит перегруппировка.

Решение через ToLookup:
public static IEnumerable<int> FindDuplicates_Lookup(List<int> data)
{
var lookup = data.ToLookup(x => x);
return lookup.Where(g => g.Count() > 1)
.Select(g => g.Key);
}


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

📍 Навигация: ВакансииЗадачиСобесы

🐸Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
6