静态单例内联初始化

时间:2017-01-30 13:36:19

标签: c# static initialization singleton

根据this question,应该保证我使用的静态字段被初始化:

10.4.5.1静态字段初始化:

  

类的静态字段变量初始值设定项对应于a   以文本顺序执行的分配顺序   它们出现在类声明中。如果是静态构造函数   (第10.11节)存在于类中,执行静态字段   初始化程序在执行该静态之前立即发生   构造函数。否则,执行静态字段初始值设定项   在第一次使用静态之前依赖于实现的时间   该类的领域

我遇到了一个奇怪的情况,这似乎并非如此。我有两个类彼此循环依赖,并且抛出NullReferenceException

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

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
{
}

如果您按照其他question中的建议取消注释静态构造函数,问题仍然存在。使用此代码在TypeInitializationException的{​​{1}} NullRefernceException获得InnerException SynchronizeSessionManager.GetInstance().GetAllActiveSessions()的{​​{1}}:

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

控制台输出:

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 ......

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

4 个答案:

答案 0 :(得分:5)

看看IL:

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

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

您可以通过创建静态Initialize方法来解决此问题,该方法执行即时调用。

试试这个:

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

    _instance.RecoverState();
}

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

    _instance.Synchronize();
}

答案 1 :(得分:3)

如果您看一下:weights[np.newaxis].T ,它有两个重要步骤。

private static SessionManager _instance = new SessionManager()

如果你尝试在asignation之前使用_instance(正如你所做的那样),那么它就是null。而它让你的NRE得到了。你可以打破这个反向调用分裂构造函数的行为,如下所示:

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

答案 2 :(得分:2)

你正在匆匆忙忙地进行某些递归:

  1. SessionManager _instance = new SessionManager();此行调用了一些以SessionManagerDatabase.GetInstance()

  2. 调用结束的方法
  3. 这也是如此,最后回复SessionManager.GetInstance()

  4. 这会导致问题,因为它需要_instanceSessionManager变量中保存的有效值,但此时您还没有真正完成方法调用链以便为_instance提供适当的价值,从而导致NullReferenceException

答案 3 :(得分:2)

您的示例中发生的是在静态字段初始化期间根据规范调用实例构造函数。但是构造函数因NullReferenceExeption而失败,因为它试图通过GetInstance()调用获取对_instance的引用。请注意,_instance尚未初始化 - 初始化过程正在进行中。因此,实例构造函数由于上述问题而失败,因此它不构造/初始化_instance字段。因此,您应该尝试从实例构造函数中获取静态_instance。