NullReferenceException from static singleton initialization

In accordance with this question, it must be guaranteed that the static fields that I use are initialized:

10.4.5.1 Initialization of a static field:

The initializers of a static variable of a class field correspond to the sequence of assignments executed in the text order in which they appear in the class declaration. If a static constructor (Section 10.11) exists in the class, the execution of the static field is done by initializers immediately before the execution of this static constructor. Otherwise, the static field initializers are executed at the implementation-dependent time until the first use of the static field of this class .

I came across a strange case when this does not seem to be true. I have two classes that are circularly dependent on each other and where a NullReferenceException .

I was able to reproduce this problem in the following simplified example, 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 remains if you uncomment the static constructors as suggested in another question . Use this code to get a TypeInitializationException with a NullRefernceException as an InnerException in Synchronize in 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 cyclical dependency (it is not so obvious in the source code), but I still do not understand why the code does not initialize singletones. What would be the best approach for this use case other than avoiding circular dependencies?

+5
source share
4 answers

Look at IL:

 IL_0001: newobj instance void SO.Program/SessionManager::.ctor() IL_0006: stsfld class SO.Program/SessionManager SO.Program/SessionManager::_instance 

Here you see that calling the static constructor has two steps. First, it initializes a new instance, and then assigns it. This means that when you make interworking calls that depend on the existence of an instance, you get stuck. It is still in the middle of creating the instance. After that, you can call it.

You can get rid of this by creating a static Initialize method that calls.

Try the following:

 static SessionManager() { _instance = new SessionManager(); _instance.RecoverState(); } static SessionManagerDatabase() { _instance = new SessionManagerDatabase(); _instance.Synchronize(); } 
+5
source

If you look at this: private static SessionManager _instance = new SessionManager() , it takes two important steps.

 1.- Initialization (new SessionManager()). 2.- The asignation(_instance = the obj). 

If you try to use _instance before asignation (like you), it is null. And this is your NRE. You can break up this callback, separating the constructor behavior as follows:

 public class SessionManager { private static SessionManager _instance; static SessionManager() { _instance = new SessionManager(); _instance.RecoverState(); } public static SessionManager GetInstance() { return _instance; } public SessionManager() { Console.WriteLine($"{nameof(SessionManager)} constructor called"); // remove RecoverState() call } 
+3
source

You rush and you have some kind of recursion:

  • SessionManager _instance = new SessionManager(); this line calls some methods that end with a call to SessionManagerDatabase.GetInstance()

  • It also does the same and ends with a call to SessionManager.GetInstance()

  • This causes a problem because it requires a valid value contained in the _instance variable in the SessionManager , but at this point you really did not end the chain of method calls to give the correct _instance value by raising a NullReferenceException .

+1
source

What happens in your example is that the instance constructor is called during the initialization of the static field in accordance with the specification. But the constructor fails with NullReferenceExeption because it tries to get the _instance reference by calling GetInstance (). Note that _instance is not yet initialized - the initialization process is in progress. Therefore, the instance constructor crashes due to the problem above and therefore does not create / initialize the _instance field. Therefore, briefly, you should try to get a static _instance from your instance constructor.

+1
source

Source: https://habr.com/ru/post/1263580/


All Articles