Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NullReferenceException from static singleton inline initialization

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?

like image 645
Tim Schmelter Avatar asked Jan 30 '17 13:01

Tim Schmelter


4 Answers

You're rushing the steps and you have some sort of recursion going on:

  1. SessionManager _instance = new SessionManager(); this line calls some methods which ends with a call to SessionManagerDatabase.GetInstance()

  2. This also does the same and ends with a call back to SessionManager.GetInstance()

  3. This causes the problem since it requires a valid value held in the _instance variable in SessionManager, but at that point you haven't really finished the chain of method calls in order to give proper value to _instance thus causing NullReferenceException.

like image 29
Deadzone Avatar answered Sep 22 '22 00:09

Deadzone


Look at the 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 the call to the static constructor has two steps. It first initializes a new instance, and then it assigns it. That means that when you do cross-class calls which depend on the existence of the instance, you are stuck. It is still in the middle of creating the instance. After that it can be called.

You might get out of this by creating a static Initialize method, which does the instantation calls.

Try this:

static SessionManager()
{
    _instance = new SessionManager();

    _instance.RecoverState();
}

static SessionManagerDatabase()
{
    _instance = new SessionManagerDatabase();

    _instance.Synchronize();
}
like image 200
Patrick Hofman Avatar answered Sep 21 '22 00:09

Patrick Hofman


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

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

If you try to use _instance before the asignation (as you do), it's null. And It thows your NRE. You can break this back call spliting the constructor behaviour like this:

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
    }
like image 32
David Pérez Cabrera Avatar answered Sep 19 '22 00:09

David Pérez Cabrera


What happens in your example is that the instance constructor is called during the static field initialization as per specification. But the constructor fails with NullReferenceExeption because it tries to obtain reference to _instance by the GetInstance() call. Please note that the _instance isn't initialized yet - the initialization process is in the progress. Consequently the instance constructor fails because of problem above and therefore it doesn't construct/initialize the _instance field. So briefly you should try to get the static _instance from your instance constructor.

like image 38
Dmytro Mukalov Avatar answered Sep 20 '22 00:09

Dmytro Mukalov