静态单例内联初始化的NullReferenceException

Tim*_*ter 6 c# singleton static initialization

根据这个问题,应该保证我使用的静态字段被初始化:

10.4.5.1静态字段初始化:

类的静态字段变量初始值设定项对应于以它们出现在类声明中的文本顺序执行的赋值序列.如果类中存在静态构造函数(第10.11节),则在执行该静态构造函数之前立即执行静态字段初始值设定项.否则,静态字段初始化器在第一次使用该类的静态字段之前的实现相关时间执行.

我遇到了一个奇怪的情况,这似乎不是真的.我有两个类相互循环依赖,NullReferenceException抛出a的地方.

我能够在以下简化的示例中重现此问题,看看:

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
{
}
Run Code Online (Sandbox Code Playgroud)

如果您按照另一个问题中的建议取消注释静态构造函数,问题仍然存在.使用此代码来获得一个TypeInitializationExceptionNullRefernceExceptionInnerExceptionSynchronizeSessionManager.GetInstance().GetAllActiveSessions():

static void Main(string[] args)
{
    try
    {
        var sessionManagerInstance = SessionManager.GetInstance();
    }
    catch (TypeInitializationException e)
    {
        Console.WriteLine(e);
        throw;
    }
}
Run Code Online (Sandbox Code Playgroud)

控制台输出:

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 ......
Run Code Online (Sandbox Code Playgroud)

我知道这里存在某种循环依赖(在原始代码中不那么明显),但我仍然不明白为什么代码无法初始化单例.除了避免循环依赖之外,这个用例的最佳方法是什么?

Pat*_*man 5

看看IL:

IL_0001:  newobj     instance void SO.Program/SessionManager::.ctor()
IL_0006:  stsfld     class SO.Program/SessionManager SO.Program/SessionManager::_instance
Run Code Online (Sandbox Code Playgroud)

在这里,您可以看到对静态构造函数的调用有两个步骤.它首先初始化一个新实例,然后分配它.这意味着当你进行依赖于实例存在的跨类调用时,你就会陷入困境.它仍然在创建实例的过程中.之后可以调用它.

您可以通过创建一个静态Initialize方法来完成此操作,该方法执行即时调用.

试试这个:

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

    _instance.RecoverState();
}

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

    _instance.Synchronize();
}
Run Code Online (Sandbox Code Playgroud)