C# 1001 notes
6.63K subscribers
419 photos
14 videos
2 files
363 links
Регулярные короткие заметки по C# и .NET.

Просто о сложном для каждого.

admin - @haarrp
Download Telegram
Полноценный Lisp можно уместить всего в 99 строк C.

Внутри при этом:

- 21 примитив
- REPL
- сборщик мусора Cheney GC
- числа с плавающей точкой
- указатели
- типы значений

И всё это без `struct`-тегов, без внешних библиотек и без обычных динамических аллокаций для представления объектов.

Главный трюк - NaN boxing.

IEEE 754 оставляет много битов внутри специальных NaN-значений. Их можно использовать как скрытое хранилище:

- часть битов кодирует тип
- до 48 бит можно использовать под указатель
- обычные double при этом остаются обычными числами

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

Очень красивый пример того, как устройство IEEE 754 можно использовать для построения компактного рантайма языка.
.NET и Владимир: отличный повод совместить митап и выходные в городе с историей

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

В программе доклады от разработчиков Altenar и Рови Тех (часть Т-Банка):
— Async/await — асинхронность, которую можно читать.
— Как не терять данные при асинхронной интеграции программных систем.
— EF Core + PostgreSQL: за рамками CRUD.


Кстати, ещё на митапе запланированы активности для программистов: адаптации известных игр ("Сапер", "Крестики-нолики", "Гонки"), где для победы понадобится понимание математики и вероятностей.

Участие бесплатное, а ещё можно сэкономить на проезде — организаторы разыграют несколько билетов на "Ласточку" среди участников.

Для участия в розыгрыше:
1. Зарегистрируйтесь на митап.
2. Напишите «Хочу на .NET-митап на Ласточке» на почту aleksei.korneev@altenar.com с почты, указанной при регистрации.

Итоги розыгрыша подведём 14 сентября.


Регистрируйтесь, планируйте поездку и до встречи во Владимире!
Forwarded from C# (C Sharp) programming
✔️ Polly становится платной, но у .NET остаётся бесплатная альтернатива

Polly много лет была одной из главных библиотек .NET для устойчивости к временным сбоям: retry, timeout, fallback, rate limiting и circuit breaker.

Теперь Polly движется в сторону платной модели, но Microsoft.Resilience по-прежнему можно использовать бесплатно.

С .NET 8 работа с resilience стала заметно проще: появился новый API Polly и официальные библиотеки Microsoft для построения resilience pipelines.

Что можно настроить:

- Retry
- Fallback
- Timeout
- Rate limiting
- Circuit breaker

Хороший разбор того, как собирать устойчивые cloud-приложения на современном .NET:

https://milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet
Please open Telegram to view this post
VIEW IN TELEGRAM
⚡️ Задачи с технических собеседований в одном репозитории

Tech-OA-Interview-Questions - подборка задач из онлайн-тестирований и интервью в технологических компаниях. Пригодится для подготовки к алгоритмическим этапам отбора.

OA, или Online Assessment, - тестирование, которое кандидат обычно проходит перед собеседованиями. В репозитории собраны материалы для подготовки к таким заданиям, в том числе к отбору в Amazon.

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

https://github.com/perixtar/Tech-OA-Interview-Questions
Согласны ?
🔧 Как передать многострочный PEM через Aspire в ASP.NET Core

Сертификаты и ключи в PEM содержат переносы строк, которые могут создавать проблемы при передаче через параметры Aspire. Damien Bod показал обходной вариант для локальной разработки с User Secrets и развёртывания в Azure.

Схема простая:

* закодировать PEM в однострочный Base64
* сохранить значение в конфигурации AppHost под ключом Parameters:ИмяПараметра
* объявить приватный ключ через AddParameter(..., secret: true)
* передать параметр приложению через WithEnvironment
* в ASP.NET Core декодировать Base64 обратно в исходный PEM

В статье есть helper-класс для преобразования, настройка AppHost и пример создания сертификата через X509Certificate2.CreateFromPem.

Base64 сохраняет переносы строк при передаче. Приватный ключ после кодирования остаётся секретом: Base64 не обеспечивает шифрование.

https://damienbod.com/2026/09/01/using-multiline-parameters-for-aspire-and-asp-net-core-with-user-secrets-and-azure-default-deployments/
🔍Тестовое собеседование с Senior C# разработчиком уже завтра

15 сентября(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle C# разработчика.

Как это будет:
📂 Александр Моргунов, старший C# разработчик с опытом 7+ лет в Европейских высоконагруженных сервисах, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Александр будет комментировать каждый ответ респондента, чтобы дать понять, чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Александру

Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для C# разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.

Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_csharp_bot

Реклама.
О рекламодателе.
Please open Telegram to view this post
VIEW IN TELEGRAM
# C# 14: конкурентный кеш без race condition

Реализуйте потокобезопасный кеш:


public sealed class AsyncCache<TKey, TValue>
where TKey : notnull
{
public Task<TValue> GetOrCreateAsync(
TKey key,
Func<CancellationToken, Task<TValue>> factory,
CancellationToken cancellationToken = default)
{
// TODO
}
}


Условия: если 100 потоков одновременно запросят один key, factory должна выполниться только один раз. Остальные ждут тот же Task. Если вычисление завершилось ошибкой - запись удаляется из кеша. Отмена одного клиента не должна отменять работу для остальных. Глобальный lock использовать нельзя.

Вопрос: как избежать race condition между созданием общего Task и удалением упавшего значения из кеша?
# C# 14: хитрая задача на ArrayPool, IAsyncEnumerable и время жизни памяти

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

Наивная реализация:


static async IAsyncEnumerable<ReadOnlyMemory<byte>> ReadLinesAsync(
Stream stream,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);

try
{
while (true)
{
int read = await stream.ReadAsync(buffer, cancellationToken);

if (read == 0)
yield break;

yield return buffer.AsMemory(0, read);
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}


Использование:


await foreach (var data in ReadLinesAsync(stream))
{
queue.Add(data);
}


Код компилируется и выглядит эффективно.

Но в production данные внутри queue иногда внезапно меняются или повреждаются.

## Вопрос

Почему?

---

# Проблема

ReadOnlyMemory<byte> не владеет памятью.

Он всего лишь указывает на:


byte[] buffer


А этот массив взят из:


ArrayPool<byte>.Shared


После следующего:


ReadAsync(buffer)


содержимое массива перезаписывается.

А после:


ArrayPool<byte>.Shared.Return(buffer);


массив вообще может получить другой поток.

Получается:


yield memory

consumer сохраняет ссылку

buffer переиспользуется

старый ReadOnlyMemory показывает новые данные


Это логический аналог use-after-free, хотя runtime C# остаётся memory-safe.

---

# Задача

Исправьте API так, чтобы:

- использовался пул памяти;
- данные можно было безопасно хранить после yield;
- не происходило скрытого копирования каждого сообщения;
- consumer явно управлял временем жизни буфера;
- отмена работала через CancellationToken.

Подсказка: используйте


IMemoryOwner<byte>


---

# Один из вариантов решения


public sealed class Message : IDisposable
{
private IMemoryOwner<byte>? _owner;

public ReadOnlyMemory<byte> Data { get; }

public Message(IMemoryOwner<byte> owner, int length)
{
_owner = owner;
Data = owner.Memory[..length];
}

public void Dispose()
{
_owner?.Dispose();
_owner = null;
}
}


Чтение:


static async IAsyncEnumerable<Message> ReadAsync(
Stream stream,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
while (true)
{
IMemoryOwner<byte> owner =
MemoryPool<byte>.Shared.Rent(4096);

int read;

try
{
read = await stream.ReadAsync(
owner.Memory,
cancellationToken);
}
catch
{
owner.Dispose();
throw;
}

if (read == 0)
{
owner.Dispose();
yield break;
}

yield return new Message(owner, read);
}
}


Consumer:


await foreach (var message in ReadAsync(stream, ct))
{
using (message)
{
Process(message.Data);
}
}


Теперь память возвращается в pool только тогда, когда consumer закончил с сообщением.

---

# Главная ловушка

Даже такой код опасен:


ReadOnlyMemory<byte> saved;

await foreach (var message in ReadAsync(stream))
{
using (message)
{
saved = message.Data;
}
}

Console.WriteLine(saved.Length);


После Dispose() память больше не принадлежит consumer.

Сам ReadOnlyMemory<byte> технически существует, но использовать его содержимое уже нельзя.

---

# Вопрос уровня Senior

Как изменить API так, чтобы разработчику было сложнее случайно сохранить ReadOnlyMemory<byte> после Dispose()?

Дополнительно подумайте:


ArrayPool<T>
vs
MemoryPool<T>
vs
обычный byte[]


и ответьте, когда zero-copy действительно быстрее, а когда управление lifetime становится дороже простой копии.
Эта задача проверяет понимание Memory<T>, pooling, ownership, IAsyncEnumerable, cancellation и одной из самых неприятных категорий ошибок высокопроизводительного C# — когда GC защищает объект от удаления, но не защищает вас от повторного использования его содержимого.