Według this question należy zagwarantować, że pola statyczne, które używam są inicjowane:NullReferenceException z singleton statyczne inicjalizacji inline
10.4.5.1 Static inicjalizacji pola:
Pole statyczne zmienne inicjalizatory z klasą odpowiadać sekwencja przydziałów, które są wykonywane w kolejności tekstowej w , które pojawiają się w deklaracji klasy. Jeśli w klasie istnieje konstruktor statyczny (sekcja 10.11), wykonywanie inicjalizatorów pól statycznych następuje bezpośrednio przed wykonaniem tego statycznego konstruktora . W przeciwnym razie inicjatory pól statycznych są wykonywane pod zależnym od implementacji czasem przed pierwszym użyciem pola statycznego tej klasy.
Spotkałem się z dziwnym przypadkiem, w którym nie wydaje się to prawdą. Mam dwie klasy, które mają zależność cykliczną od siebie i gdzie jest zgłaszany NullReferenceException
.
udało mi się odtworzyć ten problem w następujący uproszczony próbkę, rzucić okiem:
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
{
}
Problemem pozostaje, jeśli odkomentowaniu statycznych konstruktorów jak zasugerowano w drugiej question. Użyj tego kodu, aby uzyskać TypeInitializationException
z NullRefernceException
jako InnerException
w Synchronize
na SessionManager.GetInstance().GetAllActiveSessions()
:
static void Main(string[] args)
{
try
{
var sessionManagerInstance = SessionManager.GetInstance();
}
catch (TypeInitializationException e)
{
Console.WriteLine(e);
throw;
}
}
konsoli:
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 ......
Rozumiem, że istnieje jakaś zależność cykliczna tutaj (w oryginalnym kodzie nie tak oczywiste), ale wciąż nie rozumiem, dlaczego kod nie inicjuje singletonów. Jakie byłoby najlepsze podejście do tego przypadku użycia, oprócz unikania zależności cyklicznych?
To dziwne, bym się spodziewał zobaczyć 'StackOverflowException'. Generalnie nie jest dobrym pomysłem robienie czegoś zbyt wyrafinowanego podczas konstruktorów, szczególnie gdy każdy jest singletonem, a każdy ostatecznie wywołuje drugiego, zanim w pełni zakończy inicjację. Być może zajrzyj do _deferred initialization_? – MickyD
Jeśli spojrzysz na to: 'private static SessionManager _instance = new SessionManager()', ma dwa ważne kroki. 1.- Inicjalizacja ('new SessionManager()') i 2 asignation ('_instance = the obj'). jeśli spróbujesz użyć '_instance' przed przypisaniem (tak jak to robisz), jest to wartość pusta. I pokazuje twój NPE. –