TGViewer
Channel Public Channel
Библиотека шарписта | C#, F#, .NET, ASP.NET

Библиотека шарписта | C#, F#, .NET, ASP.NET

@csharpproglib

Все самое полезное для C#-разработчика в одном канале.

Наши курсы: https://clc.to/y3LDtw

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

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

РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Subscribers
21.7K
Photos
2.9K
Videos
46
Links
5.4K

Showing posts older than #6677 · Back to latest

Older Posts 20 shown
Post #6676 3.87K
🖨 Добавляем картинку в 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
  • 👍 14
  • ❤ 5
Post #6674 4.32K
💻 Windows 8 в Linux

Win8DE воссоздает интерфейс Windows 8 для Wayland. Идеально, если есть ностальгия по fluid-анимациям, но без возврата к устаревшей ОС.

Утилита для обоев, экран блокировки, меню запуска, OSD для громкости и яркости, настройки обоев. Без бесполезного шарм-меню.

➡️ Вернуть Windows 8

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

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

#async_news
  • 🥱 8
  • ❤ 3
  • 👍 2
Post #6673 4.34K
📎 Когда хочется коротко, но выходит долго

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
  • 👍 21
  • ❤ 1
Post #6672 4.06K
📱 Виджеты для Android в .NET MAUI

Microsoft показала, как делать виджеты для Android-приложений на .NET MAUI. Разбираемся, что к чему.

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

Раньше для виджетов приходилось лезть в Java или Kotlin. Теперь можно на C#.

Как устроено

Виджет работает отдельно от приложения. Даже когда приложение закрыто, виджет продолжает показывать данные и реагировать на действия.

Три основных части:

AppWidgetProvider — следит за событиями виджета (создали, обновили, удалили)
RemoteViews — рисует интерфейс виджета
BroadcastReceiver — ловит клики и другие действия пользователя

Вы создаёте класс на C#, прописываете его в манифесте Android, рисуете layout в XML. Стандартная Android-разработка, просто на C#.

➡️ Блог Microsoft

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

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

#il_люминатор
  • 👍 15
Post #6670 4.37K
👀 Сортировать все ради одного значения — это дорогая привычка

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


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

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


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

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

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

#sharp_view
  • 👍 33
  • 🥱 5
Post #6669 4.27K
😊 Прощаемся с 2025

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

Админ врывается в 2026 и вас зовёт за собой. Расскажите чем занимались на праздниках👇

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

🐸Библиотека шарписта
  • ❤ 2
Post #6668 4.54K
✏️ Не копируйте gitignore, генерируйте

Мы раньше кидали эту команду. dotnet new gitignore делает все за вас. В ней уже прописаны все типичные исключения: каталоги bin/, obj/, кэш NuGet, файлы публикаций, временные артефакты IDE и прочие служебные данные, которые не должны попадать в репозиторий.

Не нужно искать шаблон на GitHub или копировать его вручную — всё доступно из коробки.

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

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

#лучшее_из_библиотеки_2025
  • 👍 44
  • ❤ 2
Post #6667 4.14K
⚙️ Передаём данные между потоками

В C# есть несколько способов передать данные из одного потока в другой. Выбор зависит от требований к производительности, удобству и безопасности. Несколько распространенных вариантов:

1️⃣ Использование BlockingCollection<T>

Это потокобезопасная коллекция, позволяющая передавать данные от одного потока к другому.
var collection = new BlockingCollection<int>();

// Поток-поставщик (Producer)
Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
collection.Add(i);
Console.WriteLine($"Производитель: добавил {i}");
Thread.Sleep(500);
}
collection.CompleteAdding();
});

// Поток-потребитель (Consumer)
Task.Run(() =>
{
foreach (var item in collection.GetConsumingEnumerable())
{
Console.WriteLine($"Потребитель: получил {item}");
}
}).Wait();


2️⃣ Использование TaskCompletionSource<T>

Когда нужно передать значение между потоками в будущем.

var tcs = new TaskCompletionSource<int>();

// Поток-поставщик
Task.Run(() =>
{
Thread.Sleep(2000);
tcs.SetResult(42);
});

// Поток-потребитель
int result = await tcs.Task;
Console.WriteLine($"Получено: {result}");


3️⃣ Использование ConcurrentQueue<T>

Если нужно неблокирующее хранилище данных.

var queue = new ConcurrentQueue<int>();

// Поток-поставщик
Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
queue.Enqueue(i);
Console.WriteLine($"Добавлено {i}");
Thread.Sleep(500);
}
});

// Поток-потребитель
Task.Run(() =>
{
while (true)
{
if (queue.TryDequeue(out int item))
{
Console.WriteLine($"Получено {item}");
}
Thread.Sleep(100);
}
}).Wait();


4️⃣ Использование Channel<T> (System.Threading.Channels)

Альтернативный подход к BlockingCollection<T>

var channel = Channel.CreateUnbounded<int>();

// Поток-поставщик
_ = Task.Run(async () =>
{
for (int i = 0; i < 10; i++)
{
await channel.Writer.WriteAsync(i);
Console.WriteLine($"Производитель: {i}");
await Task.Delay(500);
}
channel.Writer.Complete();
});

// Поток-потребитель
await foreach (var item in channel.Reader.ReadAllAsync())
{
Console.WriteLine($"Потребитель: {item}");
}


💬 Какой вариант используете в проде?

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

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

#лучшее_из_библиотеки_2025
  • 🤩 7
  • ❤ 5
Post #6666 4.05K
👨‍💻 Жизненный цикл потока в C#

В C# потоки управляются через класс Thread из пространства имен System.Threading. Поток проходит несколько этапов на протяжении своего жизненного цикла. Давайте разберём эти этапы.

— Unstarted. Не запущен

Поток создан, но ещё не запущен. Он находится в этом состоянии сразу после инициализации объекта Thread, но до вызова метода Start().
Thread thread = new Thread(MyMethod); 
// Поток создан, но не запущен


— Running. Выполняется


Поток начинает выполнение после вызова Start(). В этом состоянии поток выполняет код, переданный в качестве делегата.

thread.Start(); 
// Поток запущен и выполняется


— Waiting. Ожидание

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

• Thread.Sleep() — поток засыпает на заданное время.
• Monitor.Wait() или lock — поток ожидает захвата монитора.
• Thread.Join() — поток ожидает завершения другого потока.

Thread.Sleep(1000); 
// Поток приостановлен на 1 секунду


— Blocked. Заблокирован

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

— Stopped. Остановлен

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

thread.Join(); 
// Основной поток ожидает завершения


Дополнительные состояния

— Background. Фоновый поток

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

thread.IsBackground = true; 
// Устанавливаем поток как фоновый


— Suspended. Приостановлен (устарело)

Метод Suspend() приостанавливал выполнение потока, но был удалён из новых версий .NET из-за возможных проблем с безопасностью.

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

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

#лучшее_из_библиотеки_2025
  • ❤ 7
  • 👍 5
Post #6665 4.26K
🛠 RabbitMQ плюс MassTransit

Мы уже давали мини гайд по RabbitMQ и MassTransit, и сейчас повторяем его как шпаргалку. Это один из самых прямых способов завести асинхронное общение сервисов в .NET без ручной возни с протоколом и очередями.

Какие инструменты нужны

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

• MassTransit. Библиотека для .NET, которая предоставляет простой и удобный API для работы с брокерами сообщений.

Как использовать их вместе

1️⃣ Устанавливаем RabbitMQ

RabbitMQ можно развернуть в Docker:
docker run -d --hostname my-rabbit --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:management


2️⃣ Настраиваем MassTransit

Необходимо зарегистрировать MassTransit и подключить его к RabbitMQ:
builder.Services.AddMassTransit(x =>
{
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host("localhost", "/", h =>
{
h.Username("guest");
h.Password("guest");
});
});
});


Этот код создает подключение к RabbitMQ, которое позволяет сервису отправлять и получать сообщения.

3️⃣ Создаем производителя

Производитель отправляет сообщения в очередь, используя MassTransit:
public async Task SendMessage(IBus bus, string message)
{
var endpoint = await bus.GetSendEndpoint(new Uri("queue:order-queue"));
await endpoint.Send(new OrderCreated { OrderId = Guid.NewGuid(), Message = message });
}


4️⃣ Создаем потребителя

Потребитель автоматически получает и обрабатывает сообщения из очереди:
public class OrderConsumer : IConsumer<OrderCreated>
{
public Task Consume(ConsumeContext<OrderCreated> context)
{
Console.WriteLine($"Получено сообщение: {context.Message.Message}");
return Task.CompletedTask;
}
}


💾 Сохраняйте мини-гайд себе, чтобы не потерять


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

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

#лучшее_из_библиотеки_2025
  • ❤ 12
  • 🤩 1
  • 🌚 1
Post #6664 4.51K
⭐️ ValueTask в C#

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

Особенности использования ValueTask:

• Повторное ожидание запрещено

Вызывать await несколько раз для одного и того же ValueTask нельзя, так как это может привести к неожиданным результатам:
ValueTask<int> task = GetValueAsync();
int value1 = await task;
int value2 = await task; // Ошибка


• Конвертация в Task

Eсли требуется передать ValueTask в API, которое ожидает Task, можно вызвать метод .AsTask():
Task<int> task = GetValueAsync().AsTask();


• Когда использовать ValueTask:

+ Операция часто завершается синхронно.
+ Создание объекта Task может быть слишком накладным.
+ Вы пишете библиотеку с высокой производительностью.

• Когда НЕ использовать ValueTask:

- Операция всегда асинхронна.
- Производительность не является критически важной.
- Приложение не оптимизировано под работу с ValueTask.

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

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

#лучшее_из_библиотеки_2025
  • ❤ 18
Post #6662 4.82K
✅ Чек лист по LINQ, который реально помогает

Тогда мы уже выкладывали похожий чек лист, и сейчас вспомним.

Основные принципы LINQ

✓ Понимать разницу между IEnumerable<T> и IQueryable<T>
✓ Фильтровать (Where()) данные как можно раньше в цепочке вызовов.
✓ Извлекать (Select()) только нужные поля, а не всю сущность.
✓ Использовать Any() вместо Count() > 0 для проверки наличия элементов.
✓ Избегать многократных проходов по коллекции (повторных .Where(), .Select())
✓ Знать разницу между отложенным и немедленным выполнением.

Работа с Where, Select, FirstOrDefault

✓ Не использовать Where().FirstOrDefault() – просто FirstOrDefault().
✓ Вызывать Where() перед Select(), а не наоборот.
✓ Использовать FirstOrDefault(predicate), если проверяется только одно значение.

Nullable

✓ Использовать ?? для значений, которые могут быть null.
✓ Использовать DefaultIfEmpty() при GroupBy().

Избегание дублирующих данных

✓ Использовать Distinct() для уникальных значений.
✓ Использовать Union() для объединения без дубликатов.
✓ Использовать Except() и Intersect() для разницы между коллекциями.

Помните, что LINQ — это не просто удобство, а инструмент, требующий понимания его тонкостей.

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

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

#лучшее_из_библиотеки_2025
  • 👍 28
  • ❤ 6
  • 🥱 1
Post #6661 4.59K
👣 JSON в классы через специальную вставку

Мы уже показывали этот трюк, и сейчас напомним, потому что он спасает часы ручного набора классов. В Visual Studio Edit → Paste Special → Paste JSON As Classes генерирует классы из JSON в буфере, включая атрибуты сериализации.

Пример:
  "Colors": [
{
"numberKey": 1,
"isPrimary": true,
"listColors": ["Red", "Blue", "Yellow"]
},
{
"numberKey": 2,
"isPrimary": false,
"listColors": ["Purple", "Green", "Orange"]
}
]
}


Сгенерированный C# код:
public class Root
{
public Color[] Colors { get; set; }
}

public class Color
{
public int NumberKey { get; set; }
public bool IsPrimary { get; set; }
public string[] ListColors { get; set; }
}



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

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

#лучшее_из_библиотеки_2025
  • 👍 28
  • 🔥 4
  • 😁 3
  • ❤ 1
  • ⚡ 1
Post #6659 4.63K
⚙️ Scalar в ASP.NET вместо скучного Swagger

Мы уже делились этим материалом весной, и сейчас самое время напомнить. Scalar это интерактивный UI для OpenAPI, который можно подключить к ASP.NET Core и получить более приятную документацию, чем стандартный Swagger UI.

Процесс установки не сложный, а результат превзойдет все ожидания.

➡️ Прочитать статью

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

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

#лучшее_из_библиотеки_2025
  • 🥰 5
  • ❤ 2
  • ❤‍🔥 1
Post #6658 4.43K
🔢 Сортировка строк с числами

Мы уже упоминали эту новость из Preview. Сейчас она в .NET 10, и стоит напомнить, что проблема file1, file10, file2 больше не актуальна.

Суть в CompareOptions.NumericOrdering. Issue #13979 висела с 2015, и вот она решена нативно. Флаг заставляет сравнивать цифры как числа, а не посимвольно, так что последовательности вроде версий или имен файлов идут правильно.

Протестить:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

var files = new List<string> { "file10", "file2", "file1", "file20" };

// Обычная сортировка
var sortedLex = files.OrderBy(f => f).ToList();
Console.WriteLine("Лексикографическая: " + string.Join(", ", sortedLex));
// file1, file10, file20, file2

// Числовая сортировка
var comparer = StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.NumericOrdering);
var sortedNumeric = files.OrderBy(f => f, comparer).ToList();
Console.WriteLine("Числовая: " + string.Join(", ", sortedNumeric));
// file1, file2, file10, file20


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

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

#лучшее_из_библиотеки_2025
  • 👍 42
  • ❤ 1
  • 🔥 1
Post #6657 4.29K
C# в 2026-м: от Task.Run к Agent.Run

Зачем просто писать асинхронный код, если можно запустить команду автономных агентов? В новом году Enterprise-разработка переходит на рельсы мультиагентности.

Прокачайте свой стек на курсе по AI-агентам:

— реализуйте паттерн ReAct для принятия решений внутри .NET систем;
— свяжите бизнес-логику с внешними API через n8n;
— освойте протокол MCP для межсистемного взаимодействия агентов;
— внедрите RAG для мгновенной обработки корпоративных данных.

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

❄️ До 12 января действует акция «3 в 1»: курс по ИИ-агентам + 2 курса в подарок.

Стать архитектором агентов
  • 🎉 4
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →