According to this question it should be guaranteed that static fields that i use are initialized:
10.4.5.1 Static field initialization:
The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration. If a static constructor (Section 10.11) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class.
I've encountered a strange case where this doesn't seem to be true. I have two classes which have a circular dependency on each other and where a NullReferenceException
is thrown.
I was able to reproduce this issue in following simplified sample, have a look:
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
{
}
The problem still remains if you uncomment the static constructors as suggested in the other question. Use this code to get a TypeInitializationException
with the NullRefernceException
as InnerException
in Synchronize
at SessionManager.GetInstance().GetAllActiveSessions()
:
static void Main(string[] args)
{
try
{
var sessionManagerInstance = SessionManager.GetInstance();
}
catch (TypeInitializationException e)
{
Console.WriteLine(e);
throw;
}
}
Console output:
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 ......
I understand that there is some kind of circular dependency here(in original code not so obvious), but I still don't understand why the code fails to initialize the singletons. What would be the best approach for this use case apart from avoiding circular dependencies?