Напишите пример реализации Singleton на C#
Что проверяют
Умеете ли написать потокобезопасный singleton и понимаете ли, почему наивный вариант ломается. Второй слой вопроса всегда про то, осознаёте ли вы, что это глобальное состояние, и что с ним делать в тестах.
Как отвечать
Начните с самого простого корректного варианта. Рантайм гарантирует, что статические поля типа инициализируются один раз и потокобезопасно, поэтому блокировки тут не нужны вовсе:
public sealed class Config
{
private static readonly Config _instance = new();
private Config() { }
public static Config Instance => _instance;
}Если нужна именно ленивость, то есть объект создаётся при первом обращении, а не при первом касании типа, берут Lazy<T>: он потокобезопасен по умолчанию и снимает всю ручную синхронизацию.
private static readonly Lazy<Config> _lazy = new(() => new Config());
public static Config Instance => _lazy.Value;Ручная двойная проверка с lock тоже допустима, но тогда поле обязано быть volatile, иначе другой поток рискует увидеть ссылку на ещё не достроенный объект. Про эту деталь стоит сказать вслух, ради неё вопрос обычно и задают.
Где спотыкаются
Пишут проверку на null без блокировки и называют её потокобезопасной. Два потока проходят проверку одновременно, и объектов получается два, причём воспроизводится это только под нагрузкой.
Делают двойную проверку, но забывают volatile. Код выглядит каноническим и на практике чаще всего работает, из-за чего ошибку не находят годами.
Не отвечают на вопрос, нужен ли тут singleton. Глобальное состояние связывает тесты между собой, подменить реализацию нечем, а порядок инициализации становится неявным. В приложении с контейнером тот же результат даёт регистрация с временем жизни singleton, при этом класс остаётся обычным, с зависимостями через конструктор.