Собеседования C#
Open in Telegram
Канал о собеседованиях в ИТ. В основном по C# но есть и общие вопросы. По всем вопросам к @al322se . Провожу консультации по подготовке к собеседованиям в том числе для мидлов и сеньоров
Show more261
Subscribers
No data24 hours
+17 days
+830 days
Data loading in progress...
Similar Channels
No data
Any problems? Please refresh the page or contact our support manager.
Tags Cloud
Incoming and Outgoing Mentions
---
---
---
---
---
---
Attracting Subscribers
July '25
July '25
+1
in 0 channels
June '25
+249
in 0 channels
Get PRO
May '250
in 1 channels
Get PRO
April '25
+13
in 1 channels
| Date | Subscriber Growth | Mentions | Channels | |
| 02 July | 0 | |||
| 01 July | +1 |
Channel Posts
Llm и C#
Долго не писал... Очень увлекся llm ками ) Решил сюда продублировать некоторое саммари о работе с llm в c#. Пока насколько знаю на собеседованиях это не спрашивают. Но как будто было бы здорово ориентировочно разбираться что может c# в контексте llm
Semantic Kernel - https://learn.microsoft.com/en-us/semantic-kernel/overview/ по сути http api к большим нейросетям на стероидах. На самом деле по сложнее, но суть в том, что semantic-kernel ничего не запускает, а только удобный интерфейс для взаимодействия с моделями. Из интересного может дать шарповую функцию которую модель сможет запустить. Это называется планинг.
Onnx Runtime - унифицированный формат моделей и движок для запуска от Microsoft. https://onnxruntime.ai/docs/get-started/with-csharp.html
ML.NET - библиотека машинного обучения от Microsoft. Есть возможность создавать свои нейронки слои и так далее. Удобное обучение этого из коробки. Из больших моделей может работать с Onnx движком. https://dotnet.microsoft.com/en-us/apps/ai/ml-dotnet
LlamaSharp - обертка над llama.cpp (ollama и llama.cpp раньше были одним и тем же, теперь два отдельных проекта). Запускает оптимизированно Lllm и работает с ними https://github.com/SciSharp/LLamaSharp
Langchain.Net - порт LangChain на .net. Что-то вроде Semantic Kernel тоже запускает раличные апи нейронок. https://tryagi.github.io/LangChain/
Есть еще TesorFlowSharp - но он для TensorFlow моделек, а я таких почти не встречаю. TorchSharp обертка над Pytorch но замучаешься ее поднимать с моделькой.
Глобально - если c# разработчик не планирует поднимать модели - Semantic Kernel (в основном) и LangChain.NET. Если хотим запускать у себя, крутить модельки и обращаться не по апи, то onnx или LlamaSharp. Если хочется попробовать что-то локально прямо сейчас то https://github.com/microsoft/PhiCookBook или LlamaSharp примеры
| 2 | Гарантии доставки Kafka
Один из чаще всего задаваемых вопросов, когда рассказываешь про очереди - это, какие гарантии доставки поддерживает. Что это вообще такое?
AtLeastOnce - сообщение будет доставлено хотя бы раз.
AtMostOnce - сообщение будет доставлено не больше одного раза, но может и не будет доставлено
ExactlyOnce - сообщение будет доставлено ровно один раз
Как это у kafka:
AtMostOnce - используется , когда скорость важнее надежности, например для логов и метрик. Просто producer без ожидания ack (т е подвтерждения от брокера , что сообщение дошло)
AtLeastOnce - producer ждет ack от брокера когда отправляет сообещение
ExactlyOnce - как обычно самое сложное. producer должен включить индепотентность (enable.idempotence=true), чтобы сообщения не дублировались, ждать akk, указать идентификатор . И должен каждое сообщение отправлять в транзакции.
var producerConfig = new ProducerConfig
{
EnableIdempotence = true,
TransactionalId = "my-eos-producer"
Acks = Acks.All
};
using var producer = new ProducerBuilder<string, string>(producerConfig).Build();
producer.InitTransactions(TimeSpan.FromSeconds(10));
try
{
producer.BeginTransaction();
producer.Produce("eos-topic", message1);
producer.Produce("eos-topic", message2);
producer.CommitTransaction(TimeSpan.FromSeconds(10));
}
catch (KafkaException ex)
{
Console.WriteLine($"Transaction failed: {ex.Message}");
producer.AbortTransaction();
}
(код для описания, не проверял его в ide)
consumer должен читать с IsolationLevel = IsolationLevel.ReadCommitted. Это уровень изоляции их у кафки всего два, второй ReadUncommited. По факту означает, что консьюмер или читаветвсе сообщения какие есть или только от закоммиченных транзакий.
Сложна... И по факту обычно таким не пользуются. Чаще всего используют AtLeastOnce, ждут akk у producer, а у consumer убирают autocommit. Т е прочитал консьюмер 10 сообщений обработал и только потом оффсет сдвинулся. А если он упал, то оффсет остался на месте и сообщения будут читаться снова
#middle #senior | 313 |
| 3 | Чатик про llm
Понял, что давно сюда ничего не писал, хотя кое-чем хочется поделится. В последнее время играюсь с локальными нейронками. И пока очень плохо разбираюсь ))
Чтобы не засорять этот канал решил сделать отдельный. Кому интересно welcome )
https://t.me/developer_in_llm
На всякий проговорю еще раз, инфа оттуда далеко не факт , что будет правильная ) | 298 |
| 4 | Консультации
Продолжаю обзор своих консультаций. Позанимались, в этот раз получилось очень много чего обсудить прямо в рамках первого созвона. Очень классно )
#training | 363 |
| 5 | SSO
Долго думал как написать про это понял, что в рамках телеграма раскрыть не получится и решил все таки сделать обзорно. SSO - single sign on паттерн работы с авторизацией аутентификацией ролями и вот этим всем в микросервисах. Собственно когда мы нажимаем войти с помощью гугл мы используем SSO от гугла. По сути это единое место, условно единый сервис, который отвечает за права во всем кластере.
С точки зрения протоколов. Т е не реализаций, а описаний (RFC) есть OAuth/OAuth2 для авторизации(Вася может брать этот ресурс) и OpenId для аутентификации(я есть Вася), а есть Open-Id-Connect в котором все сразу.
С точки зрения библиотек. Когда-то давно все использовали IdentityServer - это реализация SSO на c#. Но с 5 версии он переименовался в Duende и стал платным( После этого почти везде где знаю использовался KeyCloack.
KeyCloack - долгое время стандарт дефакто для SSO. Написан на java. В нем ооочень много всего , но он покрывает условно все кейсы. https://www.keycloak.org/
Сейчас появились более молодые Casdoor и Zitadel
Zitadel - api first sso. Это значит что у него очень богатое апи и предполагается, что использоваться он будет именно так. У него есть OpenID Connect Certification https://zitadel.com/ ссылочка
Casdoor - ui first sso. У него сразу есть UI. Он более простой в настройке чем Zitadel. У него нет сертификации, но на нем работают довольно крупные китайские компании. Собственно предполагаю, что нету сертификации из-за того что в Китае это не нужно. https://casdoor.org/
Чтобы я выбрал если сейчас делал с нуля? Если это стартап Casdoor - по проще в настройке, может чуть менее удобен для автоматизации. Если это большой энтерпрайз то Zitadel
#senior | 471 |
| 6 | Обзор бесплатных консультаций
Я недавно искал мидлов на бесплатные консультации. В итоге все таки взял двоих, а не одного(ребята кому пока отказал, сорри=) ). Прошла первая консультация, решил поделиться небольшим фидбеком)
#training | 366 |
| 7 | TestContainers
Тема тестирования довольно большая. Обычно при вопросе о тестах хотят услышать, что как минимум вы иногда их пишите 😂 И обсудить подходы. Можно рассказать когда больше нравится писать интеграционные тесты, когда unit. Решил написать о https://testcontainers.com/?language=dotnet - точно будет в тему если будете рассказывать о том как поднять окружение для интеграционных тестов. Кратко - можно описать редис, базу очередь и так далее на c# при этом при запуске оно поднимется в docker контейнерах. А еще можно настроить у этого всего интеграцию с гитлаб, чтобы там это тоже работало.
Вот например пример кода с их сайта для postgres
using Testcontainers.PostgreSql;
namespace Customers.Tests;
public sealed class CustomerServiceTest : IAsyncLifetime
{
private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()
.WithImage("postgres:15-alpine")
.Build();
public Task InitializeAsync()
{
return _postgres.StartAsync();
}
public Task DisposeAsync()
{
return _postgres.DisposeAsync().AsTask();
}
[Fact]
public void ShouldReturnTwoCustomers()
{
// Given
var customerService = new CustomerService(new DbConnectionProvider(_postgres.GetConnectionString()));
// When
customerService.Create(new Customer(1, "George"));
customerService.Create(new Customer(2, "John"));
var customers = customerService.GetCustomers();
// Then
Assert.Equal(2, customers.Count());
}
}
#middle | 378 |
| 8 | Ищу мидла на бесплатные консультации
Обдумываю что было бы интересно освещать в канале и все чаще прихожу к тому, что это опыт реальных собеседований. Поэтому сейчас ищу одного человека с реальным опытом.Которого зовут на собесы, но предлагают слишком мало или не делают оффер. Подготовлю его бесплатно - взамен согласие на освещение(анонимное) в канале =)
На всякий -ищу одного, поэтому сорри если напишите, а уже найду(
Пишите в личку @al322se
Написало уже несколько человек, буду выбирать из них, так что стоп машина) | 448 |
| 9 | IAsyncDisposable
Вроде как обещнулся в комментариях раскрыть эту тему по этому вот. Несколько раз к ней возвращался для изучения и сейчас понял почему. Обычно на собеседованиях спрашивают или сложную теорию или "хитрости" - какие-то неочевидные вещи. Так вот с IAsyncDisposable каких -то супер скрытых камней нет.
Вот пример реализации (с сайта Microsoft):
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
ValueTask - интересно, но это в целом рекомендация для асинхронных интерфейсов использовать ValueTask, так как мы не знаем вдруг в итоге будет синхронно в реализации.
.ConfigureAwait(false) - тоже интересно... Но это тоже глобальная рекомендация, для избежания deadlock'ов при реализации библиотек использовать ConfigureAwait(false).
GC.SuppressFinalize(this) - на случай если в унаследованном классе будет деструктор.
Еще пара моментов.
1.При использовании можно(и нужно) использовать await using. Если явно не указывать область видимости через фигурные скобки, то она до конца метода.
2.Если зарегистрировать класс с IAsyncDisposable в DI контейнер, а потом сделать например вот так
using var scope = AppFactory.Services.CreateScope();
то получим исключение - "type only implements IAsyncDisposable. Use DisposeAsync to dispose the container". Все тоже достаточно логично и понятно. scope поддерживает только IDisposable и асинхронный вариант не вызовется, поэтому DI об этом предупреждает.
#junior #middle | 379 |
| 10 | Привет) Решил добавить опрос и понять какой кто контент хотел бы. Важно - можно выбрать несколько пунктов | 454 |
| 11 | Temporal
Каким образом поддерживать свои знания на уровне? Я регулярно отсматриваю жирные .net вакансии и смотрю на ключевые слова, которые там используются. Очень советую делать так же для своей роли и уровня.
Недавно за таким процессом наткнулся на Temporal - еще один workflow движок. Примерное впечатление такое - прикольный, есть ui, его можно использовать в проде, есть отдельный сервер. Проще чем Camunda, но сложнее движков, которые представляют Nuget пакет. Кстати на нем тоже можно реализовать Saga https://temporal.io/blog/saga-pattern-made-easy (с оркестратором). Но чисто ради нее тащить такую штуку слишком жирно. Лицензия MIT.
#middle #senior | 390 |
| 12 | IAsyncEnumerable
Решил сразу дополнить про вариант с асинхронным yield. Такое скорее может встретиться при лайф кодинге у мидлов. IAsyncEnumerable используется при реализации потоков данных. Например читаем из базы и отдаем по grpc стриму условно бесконечно. (Кстати неадвно такое делал и чуял, что grpc обломится в какой-то момент, но нет, работал по несколько !дней! стрим без обрыва пока не заканчивался)
public async IAsyncEnumerable<int> StreamUsersFromDatabaseAsync()
{
await using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();
var command = new SqlCommand("SELECT Id Users ORDER BY Id", connection);
await using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
yield return reader.GetInt32(0);
}
}
(код не проверял на работу, это примерно как он выглядит)
Кстати, если просто есть набор задач по получению данных перед обработкой лучше использовать Task.WhenAll
string[] results = await Task.WhenAll(new[]{GetStringTask(),GetStringTask()});
тогда предварительно все данные загрузятся одновременно, а если реализовать через IAsyncEnumerable, то загрузка следующей пачки начнется после того как закончится загрузка предыдущей
#middle | 478 |
| 13 | Возвращать результат через yield return или обычную коллекцию
Не очень популярный вопрос на собеседовании, но если зайдет разговор, то лучше бы знать как ответить. А еще это действительно прикольный инструмент, которым нужно уметь пользоваться.
Что это вообще такое - мы можем создать бесконечный итератор, который потом будем использовать в foreach, при этом значения будут генерироваться в процессе перебора
public static IEnumerable<int> GetNumbers()
{
var i = 0;
while (true)
{
i++;
yield return i;
}
}
Зачем это нужно? Самый простой известный пример - расчет факториала:
public static IEnumerable<long> FactorialSequence()
{
long factorial = 1, n = 0;
while (true)
{
if (n == 0)
yield return 1;
else
{
factorial *= n;
yield return factorial;
}
n++;
}
}
И последнее - а если у меня обычная коллекция, которую я создаю и она конечна лучше ли использовать yield или сразу возвращать готовую коллекцию? Если сразу после вызова идет материализация CreateMySimpleBoundedCollection().ToArray() - то сразу готовую. Если потом планируем цепочку вызовов CreateMySimpleBoundedCollection().FilterByMyCondition().Take(pageSize) и особенно в этой цепочке предположительно возмем не всю коллекцию, а часть - есть смысл использовать yield
#junior | 354 |
| 14 | Идемпотентность
Довольно часто слышал и учавствовал в холиварах о том зачем это надо ? Какая-то тема, которую спрашивают только на собеседовании, а в жизни это используется раз в год. Таких тем в целом много... А вот идемпотентность - нет)
Идемпотентность: если мы вызовем повторно запрос с теми же параметрами (а может не запрос, может повторно прилетит сообщение в очереди, но это реже), то он не должен делать дичь. Например если мы 2 раза вызвали создание сущности с теми же параметрами и получили 2 сущности - беда. Должно первый раз создать сущность, потом нет.
Зачастую есть полуавтоматические "повторяторы" или пользователь может нажать 2 раза поэтому нужно реально стараться делать методы идемпотентными. Для этого может использоваться "ключ идемпотнетности" - грубо говоря гуид, который кэшируется или сохраняется в базу для обработанных запросов. Если такой уже был, обработка пропускается.Есть вот такая библиотека https://github.com/ikyriak/IdempotentAPI , но сам не использовал
#middle | 325 |
| 15 | Можно ли использовать await внутри секции lock
Вообще на собеседованиях часто есть какой-то вопрос для начала обсуждения целой темы. Или цепочки следующих вопросов =) Вот это один из таких начинающих цепочку - чтобы понять насколько человек хорошо понимает, что там под капотом происходит. Ниже правильная цепочка ответов, какая должна быть.
Ответ: нет, нельзя. Конкретно такая конструкция, когда await внутри lock даже не скомпилируется.
Вопрос: Окей тогда второй вопрос, а можно ли использовать lock без await внутри просто в асинхронном методе?
Ответ: это скомпилируется, но лучше такого избегать. Асинхронные методы "отдают" поток в пулл когда операция заблокирована(например ждем ответа из базы), а lock действует на поток Thread. Получается это bad practice как вызывать синхронный .Result - мы не отпускам поток
Вопрос: А что тогда использовать?
Ответ: WaitAsync из SemaphoreSlim пример кода:
private readonly SemaphoreSlim _semaphore = new(1, 1);
async Task SafeAsyncMethod()
{
await _semaphore.WaitAsync();
try
{
// critical section
await Task.Delay(100);
}
finally
{
_semaphore.Release();
}
}
а еще в него можно передавать CancellationToken для отмены при долгом ожидании.
#junior #middle | 405 |
| 16 | Код/api медленно работает, как будешь решать ?
Вопрос обычно подразумевает рассуждение с чего начать. Здорово конечно уметь пользоваться доттрейс и писать производительный код, но для начала хотят услышать рассуждения и как локализовать проблему. Я бы рассказал так
1. Посмотреть на метрики, если есть 😄
2. Добавить логи с замерами времени
3. Обратить особое внимание на вызовы к базе
4. Использовать dotrace если найдено место которое тупит.
И вот для второго и чутка третьего пункта нашел очень крутую библиотеку - https://miniprofiler.com/ Удобный профайлер для локализации места которое тупит, еще и с UI еще и может автоматом мониторить запросы. Если во втором пункте сказать про него - сразу будет дополнительно несколько баллов
#middle #senior | 434 |
| 17 | Альтернатива MassTransit для паттерна Saga
Saga - паттерн работы с распределенными транзакциями. Один из любимых вопросов при собеседовании на senior Кратко: у каждого сервиса должно быть апи для некоторого действия и для его отката. Где-то должно хранится текущее состояние т е у каких сервисов прошло действие у каких упало с ошибкой, у каких сейчас идет откат.
Сейчас часто для этого используется https://masstransit.io/documentation/patterns/saga . Но MassTransit стал платным. Есть альтернативное новое решение: https://wolverinefx.net/guide/durability/sagas.html . В целом интересная библиотека очень рекомендую посмотреть. Лицензия у нее MIT.
#senior | 827 |
| 18 | IQueryable vs IEnumerable
Достаточно частый вопрос, чтобы проверить как понимает человек работу с EntityFramework. Причем он же может быть задан и по-другому, а цель: человек должен понимать когда происходит запрос в бд.
var someOrders = dbContext
.Orders //IQueryable
.Where(x=>x.Name=="someName"); //IQueryable
var someFilteredOrders = someOrders
.Where(x=>x.Price>100) //IQueryable
.OrderBy(x=>x.Date); //IQueryable
var materializedOrders= await someFilteredOrders.ToArrayAsync(); //Это уже IEnumerable, именно здесь происходит запрос
Т е пока мы накидываем запрос через лямба выражение - это IQueryable, по сути описание запроса. При вызове ToArray() или ToList() происходит запрос и объекты уже появляются в базе - IEnumerable. А если мы у someFilteredOrders вызовем Any() и потом по нему же сделаем foreach и еще отдельно сделаем Count() - мы сделаем 3 запроса в базу, а не 1
из интересного
foreach(var order in someFilteredOrders ){
//сделай что-нибудь
}
Здесь происходит запрос при начале foreach. А когда он заканчивается вызывается Dispose. Момент с Dispose довольно интересный, и его любят слышать на собеседованиях =)
#junior #middle | 618 |
| 19 | Как обычно проходит собеседование
Естественно все зависит от компании и от собеседующего, но расскажу как собеседуют чаще всего и как собеседую я:
1. Icebreaking - разговор про погоду, город, чтобы настроиться, проверить связь, кандидат успокоился.
2. Рассказ про опыт, начиная с последнего, обычно в очень свободной форме.
3. Собеседующий или из опыта раскручивает технические темы или идёт по списку вопросов.
3.5 Во всех компаниях где я собеседовал есть своя табличка, в которой сгруппированный список тем и которую собеседующий потом заполняет.
4. Общие вопросы вроде почему ищешь другую работу, что интересно.
Выводы:
1. Нужно обязательно репетировать рассказ про опыт, если нету возможности оплатить ментора, подойдет репетиция с другом, желательно из IT, но не обязательно.
2. Даже если работали с неактуальными технологиями, условно WinForms, нужно обязательно повторить best practice по ним. При рассказе про опыт разговор туда зайдет. Очень приятно слышать, что человек реально разбирается в том с чем работал.
3. С одной стороны рассказом про опыт можно увести собеседование в нужное русло, с другой есть табличка - стандартные вопросы вроде синхронизации потоков и работы GC все равно повторить нужно.
4. Бонус: у собеседующего нет желания вас "завалить" как в универе или школе. Скорее наоброт - найти в чем разбираетесь. Условно если сказать не работал с базами, то и не спросят и не "завалят" , но и в табличке будет 0. Нужно отвечать не работал, но читал и знаю и стараться отвечать.
#interview | 432 |
| 20 | Оконные функции
Одна из интересных фич в sql. На джуниор ее редко спрашивают, и обычно ожидают понимания, что они существуют и как работает ROW_NUMBER().
Собственно как они работают на примере ROW_NUMBER(). Есть выражение в select:
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY hire_date)as number_of_hiring,
возвращает каким по счету сотрудник был нанят в этот отдел. Группирует по отделу, внутри группы сортирует по дате найма и внутри отсортированной группы возвращает номер строки.
А теперь по сложнее.
Еще функции:
RANK() - присваивает ранг с пропусками при одинаковых значениях
DENSE_RANK() - присваивает ранг без пропусков
LAG() - доступ к предыдущей строке
LEAD() - доступ к следующей строке
FIRST_VALUE() и LAST_VALUE()
Еще ключевые слова:
ROWS - определяет окно по физическим строкам (абсолютное позиционирование)
RANGE - определяет окно по логическим значениям (относительное позиционирование по значениям)
UNBOUNDED PRECEDING - от начала раздела UNBOUNDED FOLLOWING - до конца раздела CURRENT ROW - текущая строка
И пример, по ссылке можно перейти и поиграться интерактивно https://www.db-fiddle.com/f/vmN2zRWvdr57MdocN2stoZ/0
SELECT
department_id,
employee_id,
salary,
hire_date,
-- каким по счету наняли этого сотрудника в отдел, на джуниор надо только это и то далеко не всегда
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY hire_date)as number_of_hiring,
-- Средняя зарплата в отделе
AVG(salary) OVER (PARTITION BY department_id) AS dept_avg_salary,
-- Накопительная зарплата по дате приема (все предыдущие в отделе)
SUM(salary) OVER (
PARTITION BY department_id
ORDER BY hire_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_dept_salary,
-- Разница с предыдущей зарплатой любого сотрудника в отделе (по дате приема)
salary - LAG(salary, 1) OVER (
PARTITION BY department_id
ORDER BY hire_date
) AS diff_with_previous,
-- Средняя зарплата среди 3 ближайших по времени сотрудников (предыдущий, текущий, следующий)
AVG(salary) OVER (
PARTITION BY department_id
ORDER BY hire_date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS moving_avg_salary
FROM employees;
#junior #middle #senior | 440 |
