По this question должно быть гарантировано, что статические поля, которые я использую инициализируются:NullReferenceException от статического одноплодной инициализации встроенного
10.4.5.1 Статическая инициализация поля:
статическое поле инициализаторы переменных одного класса соответствуют a последовательность присвоений, которые выполняются в текстовом порядке в , которые они отображаются в объявлении класса. Если в классе существует статический конструктор (раздел 10.11), выполнение статического поля инициализаторы происходит непосредственно перед выполнением этого статического конструктора . В противном случае инициализаторы статического поля выполняются на времени, зависящего от реализации, до первого использования статического поля этого класса.
Я столкнулся с странным случаем, когда это не похоже на правду. У меня есть два класса, которые имеют круговую зависимость друг от друга и где вызывается NullReferenceException
.
Я был в состоянии воспроизвести этот вопрос в следующем упрощенном сэмпл, имеет вид:
public class SessionManager
{
//// static constructor doesn't matter
//static SessionManager()
//{
// _instance = new SessionManager();
//}
private static SessionManager _instance = new SessionManager();
public static SessionManager GetInstance()
{
return _instance;
}
public SessionManager()
{
Console.WriteLine($"{nameof(SessionManager)} constructor called");
this.RecoverState();
}
public bool RecoverState()
{
Console.WriteLine($"{nameof(RecoverState)} called");
List<SessionInfo> activeSessionsInDb = SessionManagerDatabase.GetInstance().LoadActiveSessionsFromDb();
// ...
return true;
}
public List<SessionInfo> GetAllActiveSessions()
{
Console.WriteLine($"{nameof(GetAllActiveSessions)} called");
return new List<SessionInfo>();
}
}
public class SessionManagerDatabase
{
//// static constructor doesn't matter
//static SessionManagerDatabase()
//{
// _instance = new SessionManagerDatabase();
//}
private static readonly SessionManagerDatabase _instance = new SessionManagerDatabase();
public static SessionManagerDatabase GetInstance()
{
return _instance;
}
public SessionManagerDatabase()
{
Console.WriteLine($"{nameof(SessionManagerDatabase)} constructor called");
Synchronize();
}
public void Synchronize()
{
Console.WriteLine($"{nameof(Synchronize)} called");
// NullReferenceException here
List<SessionInfo> memorySessions = SessionManager.GetInstance().GetAllActiveSessions();
//...
}
public List<SessionInfo> LoadActiveSessionsFromDb()
{
Console.WriteLine($"{nameof(LoadActiveSessionsFromDb)} called");
return new List<SessionInfo>();
}
}
public class SessionInfo
{
}
проблема все еще остается, если раскомментировать статические конструктор, как предложено в других question. Используйте этот код, чтобы получить TypeInitializationException
с NullRefernceException
как InnerException
в Synchronize
на SessionManager.GetInstance().GetAllActiveSessions()
:
static void Main(string[] args)
{
try
{
var sessionManagerInstance = SessionManager.GetInstance();
}
catch (TypeInitializationException e)
{
Console.WriteLine(e);
throw;
}
}
Консоль вывода:
SessionManager constructor called
RecoverState called
SessionManagerDatabase constructor called
Synchronize called
System.TypeInitializationException: Der Typeninitialisierer für "SessionManager" hat eine Ausnahme verursacht. ---> System.TypeInitializationException: Der Typeninitialisierer für "SessionManagerDatabase" hat eine Ausnahme verursacht. ---> System.NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
bei ConsoleApplication_CSharp.Program.SessionManagerDatabase.Synchronize() in ......
bei ConsoleApplication_CSharp.Program.SessionManagerDatabase..ctor() in ......
bei ConsoleApplication_CSharp.Program.SessionManagerDatabase..cctor() in ......
--- Ende der internen Ausnahmestapelüberwachung ---
bei ConsoleApplication_CSharp.Program.SessionManagerDatabase.GetInstance()
bei ConsoleApplication_CSharp.Program.SessionManager.RecoverState() in ......
bei ConsoleApplication_CSharp.Program.SessionManager..ctor() in .....
bei ConsoleApplication_CSharp.Program.SessionManager..cctor() in ......
--- Ende der internen Ausnahmestapelüberwachung ---
bei ConsoleApplication_CSharp.Program.SessionManager.GetInstance()
bei ConsoleApplication_CSharp.Program.Main(String[] args) in ......
Я понимаю, что есть какая-то круговая зависимость здесь (в исходном коде не так очевидно), но я до сих пор не понимаю, почему код не инициализирует синглтоны. Что было бы лучшим подходом для этого случая использования, кроме избежания круговых зависимостей?
Странно, я бы ожидал увидеть 'StackOverflowException'. Как правило, не очень хорошо делать что-то слишком сложное во время конструкторов, особенно когда каждый из них является одиночным, и каждый в конечном итоге вызывает другого, прежде чем либо полностью завершит инициализацию. Возможно, посмотрите на _deferred initialisation_? – MickyD
Если вы посмотрите на это: 'private static SessionManager _instance = new SessionManager()', он выполняет два важных шага. 1.- Инициализация ('new SessionManager()') и 2 asignation ('_instance = obj'). если вы пытаетесь использовать '_instance' перед asignation (как и вы), оно равно null. И Он творит свой NPE. –