50 важных вопросов для собеседования по C#

Проходите собеседования с ИИ помощником Hintsage

Введение

Эта статья — подборка из 50 неочевидных и реально полезных вопросов по C# и .NET, которые часто всплывают на собеседованиях.
Ответы не ограничиваются формальными определениями: есть контекст, примеры кода и подсказки, как об этом говорить на интервью.

Вопросы для начинающих и уверенных мидлов

1. В чем разница между типами значений (value types) и ссылочными типами (reference types) в C#?

В C# все типы делятся на:

  • Типы значений (value types): int, double, bool, struct, enum и др.
  • Ссылочные типы (reference types): class, string, массивы, delegate, record class и др.

Ключевые различия:

  • Для value type переменная хранит само значение.
  • Для reference type переменная хранит ссылку на объект в куче.
  • При присваивании value type копируется значение, а reference type — ссылка.
  • Value types обычно живут в стеке или внутри других объектов, reference types — в куче (heap).
struct Point {
    public int X;
    public int Y;
}

class Person {
    public string Name { get; set; }
}

var p1 = new Point { X = 1, Y = 2 };
var p2 = p1;          // копия значений

p2.X = 100;
// p1.X == 1, p2.X == 100

var person1 = new Person { Name = "Alice" };
var person2 = person1; // копия ссылки

person2.Name = "Bob";
// person1.Name == "Bob"

На собеседовании часто спрашивают, что произойдет при передаче struct / class в метод по значению и по ссылке.


2. Что такое boxing и unboxing в C# и чем они опасны?

Boxing — упаковка значения типа значения в объект (обычно object или интерфейсный тип).
Unboxing — обратное преобразование объекта обратно в value type.
int x = 42;
object boxed = x;    // boxing
int y = (int)boxed;  // unboxing

Проблемы:

  • Каждый boxing создаёт новый объект в куче → лишние аллокации → больше работа для GC.
  • Частый boxing в горячих участках (циклы, коллекции типа ArrayList) заметно бьёт по производительности.
На практических проектах почти всегда используют обобщенные коллекции (List<int>, Dictionary<int,string>) вместо старых не generics-коллекций.

3. Чем отличаются class и struct в C#?

Основное:

  • classссылочный тип.
  • structтип значения.

Различия по поведению:

  • struct копируется целиком при присваивании и передаче в метод.
  • class копирует только ссылку на один и тот же объект.
  • struct не может наследоваться от других struct (только от ValueType), но может реализовывать интерфейсы.
  • class поддерживает наследование и полиморфизм.
struct Money {
    public decimal Amount { get; }
    public string Currency { get; }

    public Money(decimal amount, string currency) {
        Amount = amount;
        Currency = currency;
    }
}

class Order {
    public Money Total { get; set; }
}

Struct лучше использовать для маленьких, логически атомарных и часто создаваемых неизменяемых типов (например, координаты, деньги, диапазоны).


4. Что такое immutable-типы и зачем нужны неизменяемые объекты в C#?

Неизменяемый объект (immutable) — это объект, состояние которого после создания изменить нельзя.

Примеры:

  • string
  • System.DateTime
  • System.TimeSpan
  • record (обычно делают неизменяемыми)

Плюсы immutable-объектов:

  • Проще многопоточный код — нет гонок (race conditions) за состояние.
  • Проще reasoning и отладка — объект либо один, либо другой, без "полусостояний".
  • Удобно кэшировать и переиспользовать.

Пример неизменяемого record:

public record User(string Name, int Age);

var u1 = new User("Alice", 30);
var u2 = u1 with { Age = 31 }; // создаётся новый объект

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


5. Что такое record в C# и чем он отличается от class?

record (C# 9+) — это особый вид ссылочного типа с семантикой сравнения по значению по умолчанию.
public record User(string Name, int Age);

var u1 = new User("Alice", 30);
var u2 = new User("Alice", 30);

Console.WriteLine(u1 == u2); // true — сравнение по значению

Для record по умолчанию генерируются:

  • методы Equals и GetHashCode по всем свойствам;
  • операторы сравнения == и !=;
  • метод Deconstruct для удобной деконструкции в кортеж.
Обычный class по умолчанию сравнивается по ссылке (если не переопределить сравнение).

6. Объясните IDisposable и паттерн using в C#.

Интерфейс IDisposable используется для типов, которые владеют неуправляемыми ресурсами:
  • файловые дескрипторы;
  • сетевые сокеты;
  • подключения к базе;
  • нативные хендлы и т.д.
Метод Dispose() должен освобождать такие ресурсы.
using (var stream = File.OpenRead("data.txt"))
{
    // работа со stream
} // здесь stream.Dispose() будет вызван автоматически

С C# 8 можно писать:

using var connection = new SqlConnection(connString);
// Dispose вызовется в конце области видимости
Важно уметь рассказать про dispose pattern в классах, которые также имеют финализатор (~ClassName()).

7. Как работает сборщик мусора (GC) в .NET и что такое поколения?

GC в .NET — generational collector. Он делит объекты по "возрасту" на поколения:

  • Generation 0 — самые молодые объекты; сборка происходит часто.
  • Generation 1 — промежуточные.
  • Generation 2 — долгоживущие объекты (кэш, синглтоны).
Также есть Large Object Heap (LOH) для больших объектов (обычно > 85k).

Идея:

  • Большинство объектов умирает быстро.
  • Поэтому выгодно чаще собирать молодые поколения и реже трогать старые.

На собеседовании можно упомянуть:

  • что частые маленькие аллокации нормальны для .NET;
  • но большие объекты и "вечно живущие" кэши нужно контролировать, чтобы не загаживать Gen2 и LOH.

8. Что такое delegate в C# и чем он отличается от event?

delegate — тип, представляющий ссылку на метод (или несколько методов с мультикастом).
public delegate int Operation(int x, int y);

public int Sum(int a, int b) => a + b;

Operation op = Sum;
int result = op(2, 3);
event — более ограниченная оболочка вокруг делегата, предназначенная для паттерна "издатель–подписчик".
public class Button {
    public event EventHandler? Click;

    protected void OnClick() {
        Click?.Invoke(this, EventArgs.Empty);
    }
}
Снаружи обычно можно только подписаться += или отписаться -=. Непосредственный вызов события извне запрещён (инкапсуляция).

9. Что такое лямбда-выражения и анонимные методы в C#?

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

Func<int, int> square = delegate(int x) { return x * x; };

Лямбда-выражение — более компактный синтаксис для анонимного метода:

Func<int, int> square = x => x * x;

Лямбды:

  • могут захватывать внешние переменные (closures);
  • широко используются в LINQ, событиях, TPL, API ASP.NET.

10. Что такое LINQ и чем отличаются query syntax и method syntax?

LINQ (Language Integrated Query) — набор расширений и синтаксиса для декларативной работы с коллекциями, БД, XML и др.

Два вида записи:

  1. Query syntax (SQL-подобный):
var evens = from n in numbers
            where n % 2 == 0
            select n;
  1. Method syntax:
var evens = numbers.Where(n => n % 2 == 0);

Компилятор переводит query syntax в method syntax. Важно понимать отличие:

  • LINQ to Objects (работа в памяти, IEnumerable);
  • LINQ to Entities (EF Core, IQueryable, выражения транслируются в SQL).

11. В чем разница между IEnumerable и IQueryable?

  • IEnumerable<T> — интерфейс для последовательного перебора в памяти.
    Операции LINQ выполняются в приложении после получения данных.
  • IQueryable<T> — надстройка над IEnumerable<T> с деревом выражения (Expression).
    Операции могут быть переведены, например, в SQL на стороне БД.
IEnumerable<User> usersInMemory = list.Where(u => u.Age > 18);

IQueryable<User> usersQuery = dbContext.Users.Where(u => u.Age > 18);
// Where будет трансформирован в SQL
Типичная ошибка — вызвать ToList() слишком рано и "затащить" всю таблицу в память.

12. Что такое async/await в C# и как оно работает под капотом?

async/await — синтаксический сахар вокруг Task и state machine.
public async Task<string> DownloadAsync(string url)
{
    using var client = new HttpClient();
    var response = await client.GetStringAsync(url);
    return response;
}

Под капотом:

  • Компилятор превращает метод в машину состояний.
  • await "разрезает" метод на части: до ожидания и продолжение после завершения задачи.
  • Поток не блокируется, управление возвращается вызывающему коду, а продолжение запланируется позже.

На собеседовании часто спрашивают:

  • чем async отличается от многопоточности,
  • почему нельзя мешать await и .Result / .Wait().

13. В чем разница между Task, Task<T> и ValueTask?

  • Task — асинхронная операция без возвращаемого результата.
  • Task<T> — асинхронная операция, возвращающая T.
  • ValueTask<T> — облегчённая обертка, которая может хранить либо T, либо Task<T>.
public Task DoAsync() => Task.CompletedTask;

public Task<int> GetAsync() => Task.FromResult(42);

public ValueTask<int> GetFastAsync()
{
    if (/* уже есть результат */)
        return new ValueTask<int>(42);

    return new ValueTask<int>(SlowAsync());
}
ValueTask уменьшает аллокации, но сложнее в правильном использовании (нельзя многократно await-ить один и тот же ValueTask и т.п.). По умолчанию, если нет профилированной необходимости, лучше использовать Task.

14. Чем асинхронность (async/await) отличается от параллелизма (Parallel) в C#?

  • Асинхронность — про неблокирующее ожидание (IO-bound задачи).
  • Параллелизм — про использование нескольких ядер (CPU-bound задачи).
// Параллельная CPU-bound обработка
Parallel.For(0, 100, i => DoHeavyWork(i));

// Асинхронный IO-bound
await DownloadAsync(url);
Нельзя просто заменить любой цикл на Parallel.For или любую операцию на async. Нужно понимать характер нагрузки.

15. Что такое lock и какие есть альтернативы синхронизации в C#?

lock — синтаксический сахар вокруг Monitor.Enter/Exit. Обеспечивает взаимное исключение в критических секциях.
private readonly object _sync = new();

public void Increment() {
    lock (_sync) {
        _counter++;
    }
}

Альтернативы:

  • Monitor
  • Mutex, Semaphore, SemaphoreSlim
  • ReaderWriterLockSlim
  • Interlocked для атомарных операций
  • concurrent-коллекции (ConcurrentDictionary, ConcurrentQueue) и более высокоуровневые абстракции.

16. Как работает IAsyncDisposable и await using?

Для ресурсов, которые освобождаются асинхронно (например, при закрытии соединения нужно сделать сетевой вызов), есть IAsyncDisposable и DisposeAsync().
public class AsyncResource : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        // асинхронное освобождение
        await Task.Delay(100);
    }
}

await using var resource = new AsyncResource();
// ...
await using гарантирует, что DisposeAsync будет вызван с await.

17. Что такое dependency injection (DI) и как он реализован в .NET?

DI — паттерн, когда объект не создаёт свои зависимости сам, а получает их извне (через конструктор, свойства, параметры методов).

public interface ILogger {
    void Log(string message);
}

public class ConsoleLogger : ILogger {
    public void Log(string message) => Console.WriteLine(message);
}

public class Service {
    private readonly ILogger _logger;
    public Service(ILogger logger) {
        _logger = logger;
    }
}

В современной .NET (ASP.NET Core):

  • есть встроенный контейнер DI (IServiceCollection, IServiceProvider);
  • зависимости регистрируются как Singleton, Scoped, Transient.

18. Что такое attribute (атрибут) в C# и как его использовать?

Атрибут — это класс, наследуемый от System.Attribute, который добавляет метаданные к коду. Читается через рефлексию.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuditAttribute : Attribute {
    public string Action { get; }
    public AuditAttribute(string action) => Action = action;
}

[Audit("CreateUser")]
public class UserController { }

Атрибуты активно используются:

  • в ASP.NET Core (атрибуты контроллеров/действий),
  • в ORM (EF Core),
  • в сериализации (System.Text.Json, Newtonsoft.Json),
  • в валидации (DataAnnotations).

19. Как работает рефлексия (Reflection) в C# и когда её стоит избегать?

Reflection позволяет во время выполнения:

  • узнавать список сборок, типов, методов, свойств;
  • создавать экземпляры типов;
  • вызывать методы динамически.
var type = typeof(User);
var props = type.GetProperties();

foreach (var prop in props) {
    Console.WriteLine(prop.Name);
}

Минусы:

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

Если возможно, лучше использовать:

  • generics;
  • выражения (Expression);
  • source generators;
  • явный код вместо рефлексии.

20. Что такое dynamic в C# и чем он отличается от var?

  • varвывод типа во время компиляции. Тип известен компилятору.
  • dynamicдинамическая типизация. Проверки типов переносятся в runtime.
var x = 10;        // x: int
dynamic y = 10;   // y: dynamic

y = "hello";      // ок, тип меняется на runtime
dynamic удобно для:
  • взаимодействия с COM;
  • работы с динамическими языками;
  • когда тип известен только в runtime.

Но легко получить runtime-ошибку, поэтому использовать аккуратно.


21. Что такое nullable reference types и как они помогают бороться с NullReferenceException?

С C# 8 можно включить режим nullable reference types:

#nullable enable

string? maybeNull = GetOrNull();
if (maybeNull != null) {
    Console.WriteLine(maybeNull.Length);
}

string notNull = "test";
notNull = null; // warning

Суть:

  • string означает "по контракту не null".
  • string? означает "может быть null".
  • Компилятор подсвечивает потенциально опасные места.
Это статическая проверка, runtime-поведение не меняется, но количество NullReferenceException в проде сильно падает, если следовать подсказкам.

22. Как работает pattern matching в C# (оператор is, switch expressions)?

Pattern matching позволяет:

  • проверять тип и сразу делать cast;
  • проверять значения;
  • сопоставлять с образцом (property patterns, positional patterns).
object o = GetValue();

if (o is int i && i > 0) {
    Console.WriteLine($"Positive int: {i}");
}

string DescribeShape(Shape s) => s switch {
    Circle c => $"Circle with radius {c.R}",
    Rectangle { Width: var w, Height: var h } => $"Rect {w}x{h}",
    _ => "Unknown"
};

Современный C# (9/10/11) сильно расширил pattern matching, и это часто спрашивают как "признак" актуальных знаний.


23. Что такое extension methods и когда их стоит использовать?

Extension method — статический метод, который выглядит как метод экземпляра.

public static class StringExtensions {
    public static bool IsNullOrEmpty(this string? value) =>
        string.IsNullOrEmpty(value);
}

// Использование
if (name.IsNullOrEmpty()) {
    // ...
}

Нюансы:

  • объявляется в статическом классе;
  • первый параметр помечается this перед типом;
  • хорошо подходят для удобных утилит и fluent API;
  • можно "расширить" типы из BCL и сторонних библиотек.

Злоупотреблять не стоит — можно превратить код в "свалку" extension-методов.


24. В чем разница между interface и abstract class в C#?

Кратко:

  • interface описывает контракт (набор членов).
  • abstract class может содержать и контракт, и частичную реализацию + состояние.

Различия:

  • класс может реализовывать несколько интерфейсов, но наследоваться только от одного класса;
  • в абстрактном классе можно хранить поля, защищенные методы, конструкторы;
  • интерфейсы в C# 8+ могут иметь default-реализации методов, но не могут хранить состояние.

Интерфейсы — для слабой связности и полиморфизма, абстрактные классы — для общей базовой реализации.


25. Как работают generics в C# и чем они отличаются от дженериков в Java?

Generics в C# — рефицированные (reified):

  • информация о типе параметра сохраняется в runtime;
  • можно использовать typeof(T), default(T), проверять типы, делать разные реализации в зависимости от T.
public class Repository<T> where T : Entity {
    public T? FindById(Guid id) { ... }
}
В Java generics реализованы через erasure (стирание типов), поэтому нельзя, например, создать new T[10].

26. Зачем нужны ограничения обобщений (where T : ...), какие они бывают?

Ограничения позволяют сузить допустимые типы:

  • where T : struct — только value types;
  • where T : class — только ссылочные типы;
  • where T : new() — нужен public конструктор без параметров;
  • where T : SomeBaseClass — наследник указанного базового класса;
  • where T : ISomeInterface — реализует интерфейс.
public T CreateInstance<T>() where T : new()
{
    return new T();
}

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


27. Чем отличаются ref, out и in параметры в C#?

Все три — про передачу по ссылке, но с разными правилами:

  • ref — параметр должен быть инициализирован до вызова и может быть изменен внутри метода.
  • out — не обязан быть инициализирован до вызова, метод обязан присвоить значение перед выходом.
  • in — передача по ссылке только для чтения (C# 7.2+), полезно для больших struct.
void Calc(ref int x, out int y) {
    x += 10;
    y = x * 2;
}

28. Что такое value tuple (ValueTuple) и чем он лучше старого Tuple?

ValueTuple:
  • struct (меньше аллокаций, нет лишних объектов в куче);
  • поддерживает ИМЕНА элементов: (int Id, string Name);
  • интегрирован в язык (деконструкция, возврат нескольких значений).
(int Id, string Name) GetUser() => (1, "Alice");

var user = GetUser();
Console.WriteLine(user.Name);
Старый Tuple — reference type, без нормальных имён свойств (Item1, Item2).

29. Как работают индексы и диапазоны (Index, Range, операторы ^ и ..) в C#?

С C# 8 появились:

  • index[^1] — элемент с конца;
  • array[start..end] — slice (подпоследовательность).
var arr = new[] { 1, 2, 3, 4, 5 };

int last = arr[^1];      // 5
var middle = arr[1..4];  // {2, 3, 4}
Это синтаксический сахар над Index и Range.

30. Что такое Span<T> и Memory<T> и зачем они нужны?

Span<T> — "окно" на непрерывный блок памяти:
  • может ссылаться на массив, stackalloc, неуправляемую память;
  • не создает копий при нарезке подмассивов;
  • ref struct, поэтому не может жить в куче и в async.
Span<int> slice = array.AsSpan(1, 3);
Memory<T> — похожая концепция, но может храниться в куче и использоваться в асинхронном коде.

Обычно их используют в high-performance коде, парсерах, обработке больших буферов.


31. Что такое async void и почему его обычно нужно избегать?

async void:
  • нельзя await-ить;
  • исключения из него не попадают в обычную цепочку Task и обрабатываются иначе;
  • его трудно тестировать и комбинировать с другими async-операциями.
async void OnClick(object sender, EventArgs e) {
    await DoSomethingAsync();
}
Допустимое применение — обработчики событий. В остальных случаях почти всегда должен быть async Task.

32. Как работает ConfigureAwait(false) и когда его использовать?

По умолчанию await пытается вернуться в исходный контекст синхронизации (UI, ASP.NET).
await SomeAsync().ConfigureAwait(false);
ConfigureAwait(false) говорит:

"Не нужно возвращаться в исходный контекст, продолжай выполнение на любом потоке из пула."

Это:

  • уменьшает накладные расходы;
  • помогает избежать deadlock-ов в некоторых сценариях (особенно в старом ASP.NET и UI-приложениях);
  • в библиотечном коде обычно всегда ставится ConfigureAwait(false).

33. Что такое CancellationToken и как правильно отменять асинхронные операции?

CancellationToken — механизм кооперативной отмены. Создается через CancellationTokenSource и передается во все операции, которые поддерживают отмену.
public async Task DoWorkAsync(CancellationToken token)
{
    while (!token.IsCancellationRequested) {
        await StepAsync(token);
    }
}

Правильная отмена:

  • проверять IsCancellationRequested;
  • бросать OperationCanceledException при необходимости;
  • не игнорировать токен в глубине стека.

34. Что такое lock-free структуры данных и какие есть примитивы в .NET?

Lock-free структуры данных работают без традиционных блокировок (lock), используя атомарные операции (CAS).

В .NET:

  • класс Interlocked (Increment, CompareExchange);
  • ConcurrentDictionary, ConcurrentQueue, ConcurrentBag, ConcurrentStack;
  • System.Threading.Channels.

Плюсы:

  • лучше масштабируются под высокой нагрузкой;
  • меньше contention на блокировках.

Минусы:

  • сложнее имплементировать и понимать.

35. Что такое async streaming (IAsyncEnumerable<T>) и await foreach?

Позволяет асинхронно возвращать последовательность значений, не загружая всё сразу.

public async IAsyncEnumerable<int> GetNumbersAsync()
{
    for (int i = 0; i < 10; i++) {
        await Task.Delay(100);
        yield return i;
    }
}

await foreach (var n in GetNumbersAsync()) {
    Console.WriteLine(n);
}

Это удобно для стриминга данных из БД/сети, логов, парсинга больших файлов.


36. Чем отличаются middleware и filters в ASP.NET Core?

  • Middleware — компоненты конвейера обработки HTTP-запросов на уровне всего приложения:

    • логирование,
    • аутентификация,
    • CORS,
    • обработка ошибок и т.д.
  • Filters — механизм уровня MVC/Web API:

    • AuthorizationFilter,
    • ActionFilter,
    • ExceptionFilter,
    • ResultFilter.

Они оборачивают вызов конкретных контроллеров/экшенов, а не весь pipeline.


37. Что такое Source Generators в C# и зачем они нужны?

Source Generators — механизм Roslyn, который позволяет во время компиляции:

  • анализировать ваш код;
  • генерировать дополнительный C#-код.

Применение:

  • генерация сериализаторов (System.Text.Json source generators);
  • мапперы (Mapster, частично AutoMapper);
  • DI-обертки;
  • уменьшение использования рефлексии в runtime.

Это даёт производительность и безопасность, т.к. всё вычисляется на этапе компиляции.


38. Как корректно реализовать Equals, GetHashCode и оператор == для класса?

Если вы хотите сравнение по значению:

  1. Реализуйте IEquatable<T>.
  2. Переопределите Equals(object).
  3. Переопределите GetHashCode().
  4. (Опционально) перегрузите == и !=.
public class Point : IEquatable<Point> {
    public int X { get; }
    public int Y { get; }

    public bool Equals(Point? other) =>
        other is not null && X == other.X && Y == other.Y;

    public override bool Equals(object? obj) => Equals(obj as Point);

    public override int GetHashCode() => HashCode.Combine(X, Y);

    public static bool operator ==(Point? a, Point? b) =>
        Equals(a, b);

    public static bool operator !=(Point? a, Point? b) => !(a == b);
}

Важно соблюдать контракт: равные объекты должны иметь одинаковый hash code.


39. Что такое deadlock и как он может возникнуть с async/await?

Deadlock — ситуация, когда задачи/потоки вечно ждут друг друга.

Классический пример в C#:

// Потенциальный deadlock в UI/старом ASP.NET
var result = SomeAsync().Result; // или .Wait()
Если SomeAsync внутри пытается вернуться в тот же контекст синхронизации, который сейчас заблокирован .Result, получаем взаимную блокировку.

Лечение:

  • использовать await до самого верха;
  • не блокировать асинхронный код синхронно;
  • в библиотеках использовать ConfigureAwait(false).

40. Что такое "memory leak" в .NET, если есть GC?

GC удаляет неиспользуемые объекты, но не умеет угадывать, что объект вам "больше не нужен".
Если на объект есть хоть одна живая ссылка, он считается достижимым и не удаляется.

Типичные источники утечек:

  • статические коллекции, куда складывают всё подряд и никогда не чистят;
  • события, где подписчик не отписался;
  • кэши без политики очистки.

Это не "классическая" утечка, как в C/C++, но по факту — память уходит и не возвращается.


41. Как правильно работать с событиями, чтобы избежать утечек памяти?

Проблема:

  • есть долгоживущий объект (издатель события),
  • и короткоживущий подписчик,
  • подписчик подписался на событие и не отписался.

Издатель держит ссылку на делегат, а делегат — на подписчика, и GC не может удалить подписчика.

Решения:

  • явно отписываться в Dispose / Close / финализаторе;
  • использовать слабые ссылки (WeakReference) / weak events (WPF);
  • проектировать так, чтобы издатель не жил намного дольше подписчика.

42. В чем разница между Task.Run и Thread в C#?

  • Thread — "голый" поток ОС: дорого создавать, управлять, нет интеграции с TPL.
  • Task.Run — планирует задачу в пул потоков (ThreadPool), интегрирован с async/await, обработкой исключений, отменой.
// Плохо: вручную плодить потоки
new Thread(() => DoWork()).Start();

// Лучше: использовать пул потоков
await Task.Run(() => DoWork());
В реальных проектах почти всегда используют Task/TPL, а не голые Thread.

43. Что такое retry-логика с backoff и как её реализовать в C#?

Retry (повторная попытка) нужен для временных (transient) ошибок: сетевые проблемы, временная недоступность сервиса.

Простой пример c экспоненциальным backoff:

for (int attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
        await CallExternalServiceAsync();
        break;
    }
    catch (TransientException) {
        if (attempt == maxAttempts) throw;
        var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt));
        await Task.Delay(delay);
    }
}

На практике часто используются библиотеки типа Polly.


44. Что такое configuration и options pattern в ASP.NET Core?

Options pattern — типизированная работа с конфигурацией:

  1. Определяем класс настроек:
public class MyOptions {
    public string ApiKey { get; set; } = "";
    public int TimeoutSeconds { get; set; }
}
  1. Биндим его к секции в appsettings.json:
builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));
  1. Внедряем через DI (IOptions<MyOptions>, IOptionsSnapshot<MyOptions>, IOptionsMonitor<MyOptions>).
Это удобнее и безопаснее, чем постоянно дергать Configuration["Key"] строками.

45. В чем разница между синхронным и асинхронным API в EF Core?

  • ToList(), FirstOrDefault(), SaveChanges() — синхронные, блокируют поток, пока БД отвечает.
  • ToListAsync(), FirstOrDefaultAsync(), SaveChangesAsync() — асинхронные, не блокируют поток и позволяют серверу обслуживать больше запросов.

На backend-собесах часто спрашивают:

  • почему асинхронные методы предпочтительнее на высоконагруженном API;
  • почему async не делает запрос быстрее, но позволяет масштабироваться лучше.

46. Что такое DTO и почему его часто используют с AutoMapper?

DTO (Data Transfer Object) — простой класс для переноса данных между слоями/сервисами:

  • без логики,
  • только данные и простые правила валидации.

Доменные модели часто содержат поведение, сложные связи, lazy-loading и т.п., и напрямую светить их наружу (в API) не всегда безопасно / удобно.

AutoMapper:

  • позволяет автоматически маппить свойства доменной модели в DTO по соглашениям;
  • уменьшает boilerplate-код копирования.

47. Как реализовать кастомный middleware в ASP.NET Core?

Middleware — класс с методом Invoke или InvokeAsync, принимающим HttpContext.
public class LoggingMiddleware {
    private readonly RequestDelegate _next;

    public LoggingMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context) {
        Console.WriteLine($"Request: {context.Request.Path}");
        await _next(context);
    }
}

// Program.cs
app.UseMiddleware<LoggingMiddleware>();
Можно написать extension-метод UseLogging() для красивой регистрации.

48. Что такое Open/Closed Principle (OCP) и как он проявляется в C#-коде?

OCP (из SOLID):

"Программные сущности должны быть открыты для расширения, но закрыты для модификации."

В C# это часто значит:

  • выделение интерфейсов и абстракций;
  • использование DI и паттернов (стратегия, декоратор, фабрики);
  • добавление нового поведения через новые классы/компоненты, а не изменение старых.
Например, вместо switch по типу оплаты лучше иметь IPaymentStrategy и разные реализации.

49. Какие типичные архитектурные вопросы задают после технических по C#?

После языка обычно идут вопросы:

  • Как разделяете слои (Web/API, Application, Domain, Infrastructure)?
  • Используете ли DDD, CQRS, event sourcing?
  • Как подходите к логированию, метрикам, tracing?
  • Как организуете solution: проекты, зависимости?
  • Как тестируете: unit, integration, end-to-end?

Важно показать, что вы умеете применять C# и .NET в контексте архитектуры и командной разработки.


50. Как подготовиться к собеседованию по C# и .NET, опираясь на эти вопросы?

Практический чек-лист:

  • Пройдитесь по всем 50 вопросам и попробуйте:
    • объяснить вслух каждую тему простыми словами;
    • написать маленький пример кода "по памяти";
    • придумать, где вы сталкивались с этим в реальных проектах.
  • Отдельно потренируйте:
    • async/await, CancellationToken, ConfigureAwait;
    • LINQ и отличия IEnumerable/IQueryable;
    • DI, жизненные циклы сервисов;
    • ASP.NET Core (middleware, filters, конфигурация);
    • EF Core (миграции, трекинг, асинхронные запросы).

Если вы можете уверенно и спокойно объяснить все эти темы — на большинстве собеседований по C# вы будете чувствовать себя очень уверенно.

Заключение

Эти 50 вопросов закрывают не только синтаксис C#, но и важные части стека .NET: память, асинхронность, LINQ, generics, DI, ASP.NET Core, EF Core и архитектурные подходы.
Используйте их как чек-лист: дополняйте своими заметками, кодом, примерами из проектов — и превратите это в свой небольшой консект перед собеседованием.