2017-01-30 93 views
5

Według this question należy zagwarantować, że pola statyczne, które używam są inicjowane:NullReferenceException z singleton statyczne inicjalizacji inline

10.4.5.1 Static inicjalizacji pola:

Pole statyczne zmienne inicjalizatory z klasą odpowiadać sekwencja przydziałów, które są wykonywane w kolejności tekstowej w , które pojawiają się w deklaracji klasy. Jeśli w klasie istnieje konstruktor statyczny (sekcja 10.11), wykonywanie inicjalizatorów pól statycznych następuje bezpośrednio przed wykonaniem tego statycznego konstruktora . W przeciwnym razie inicjatory pól statycznych są wykonywane pod zależnym od implementacji czasem przed pierwszym użyciem pola statycznego tej klasy.

Spotkałem się z dziwnym przypadkiem, w którym nie wydaje się to prawdą. Mam dwie klasy, które mają zależność cykliczną od siebie i gdzie jest zgłaszany NullReferenceException.

udało mi się odtworzyć ten problem w następujący uproszczony próbkę, rzucić okiem:

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

Problemem pozostaje, jeśli odkomentowaniu statycznych konstruktorów jak zasugerowano w drugiej question. Użyj tego kodu, aby uzyskać TypeInitializationException z NullRefernceException jako InnerException w Synchronize na SessionManager.GetInstance().GetAllActiveSessions():

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

konsoli:

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

Rozumiem, że istnieje jakaś zależność cykliczna tutaj (w oryginalnym kodzie nie tak oczywiste), ale wciąż nie rozumiem, dlaczego kod nie inicjuje singletonów. Jakie byłoby najlepsze podejście do tego przypadku użycia, oprócz unikania zależności cyklicznych?

+1

To dziwne, bym się spodziewał zobaczyć 'StackOverflowException'. Generalnie nie jest dobrym pomysłem robienie czegoś zbyt wyrafinowanego podczas konstruktorów, szczególnie gdy każdy jest singletonem, a każdy ostatecznie wywołuje drugiego, zanim w pełni zakończy inicjację. Być może zajrzyj do _deferred initialization_? – MickyD

+3

Jeśli spojrzysz na to: 'private static SessionManager _instance = new SessionManager()', ma dwa ważne kroki. 1.- Inicjalizacja ('new SessionManager()') i 2 asignation ('_instance = the obj'). jeśli spróbujesz użyć '_instance' przed przypisaniem (tak jak to robisz), jest to wartość pusta. I pokazuje twój NPE. –

Odpowiedz

5

Spójrz na IL:

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

Tutaj widać, że wywołanie konstruktora statycznego ma dwa etapy. Najpierw inicjuje nową instancję, a następnie ją przypisuje. Oznacza to, że gdy wykonujesz połączenia między klasami, które zależą od istnienia instancji, utkniesz. Wciąż jest w trakcie tworzenia instancji. Po tym można go wywołać.

Możesz się z tego wydostać, tworząc statyczną metodę Initialize, która wywołuje wywołania.

Spróbuj tego:

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

    _instance.RecoverState(); 
} 

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

    _instance.Synchronize(); 
} 
+0

Zobacz kod w aktualizacji. Jeśli użyjesz konstruktora statycznego do podzielenia instancji z inicjalizacji, to działa. –

+0

I o "wywołaniu do konstruktora statycznego", konstruktor statyczny składa się z dwóch kroków. Poza tym oczywiście z CLR nie ma widocznego połączenia. –

+0

Oto, co mam teraz: http://ideone.com/xzceqp –

1

Ty pośpiechu kroki i masz jakieś rekursji dzieje:

  1. SessionManager _instance = new SessionManager(); linia ta wywołuje pewne metody, która kończy się wezwaniem do SessionManagerDatabase.GetInstance()

  2. To również robi to samo i kończy się oddzwonić do SessionManager.GetInstance()

  3. Powoduje to problem, ponieważ wymaga prawidłowej wartości przechowywanych w zmiennej _instance w SessionManager, ale w tym momencie naprawdę nie skończył łańcucha wywołań metod w celu nadania właściwej wartości _instance powodując NullReferenceException.

+0

Dzięki za odpowiedź. Masz rację, już to zauważyłem. Pozostaje jednak pytanie, jak uniknąć tej okrężnej zależności. +1 –

+0

@TimSchmelter Możesz przemyśleć swój projekt. Typową poprawką dla zależności cyklicznej jest używanie interfejsów. Trudno jest zaproponować inne podejście do pracy z interfejsami, ponieważ przypuszczam, że masz dużo kodu, którego nie pokazałeś. – Deadzone

+0

oczywiście masz rację, są inne sposoby, aby tego uniknąć. Ale tak naprawdę Patrick i David już pokazali, jak łatwo to naprawić. Usuń zależność od konstruktora i wywołaj metodę, która odwołuje się do innej klasy z konstruktora statycznego. Ponieważ używany jest tylko singleton, nie ma potrzeby wywoływania go z konstruktora instancji. –

1

Co się dzieje w twoim przykładzie jest to, że konstruktor instancji jest wywoływany podczas inicjalizacji pola statycznego zgodnie ze specyfikacją. Konstruktor jednak kończy się niepowodzeniem z NullReferenceExeption, ponieważ próbuje uzyskać odwołanie do instancji przez wywołanie GetInstance(). Należy zauważyć, że instancja _instance nie została jeszcze zainicjalizowana - proces inicjowania jest w toku. W związku z tym konstruktor instancji kończy się niepowodzeniem z powodu powyższego problemu i dlatego nie tworzy/nie inicjuje pola _instance. Dlatego krótko powinieneś spróbować pobrać statyczną _instance z twojego konstruktora instancji.

+0

Dzięki za odpowiedź. Masz rację, już to zauważyłem. Pozostaje jednak pytanie, jak uniknąć tej okrężnej zależności. Nie rozumiem _ "spróbuj uzyskać statyczną _instance z twojego konstruktora instancji" _ +1 –

+0

Przepraszam, chciałem powiedzieć, że nie powinieneś próbować. Jeśli chodzi o rozwiązanie, proponuję użyć Lazy zamiast singletonu w oparciu o pole statyczne. Lazy gwarantuje bezpieczne wykonanie pojedynczej singletowej funkcji inicjalizacji, w której można uzyskać wszystkie informacje o sesjach z bazy danych. –

3

Jeśli spojrzeć na to: private static SessionManager _instance = new SessionManager(), ma dwa ważne etapy.

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

Jeśli spróbujesz użyć _instance przed asignation (jak to zrobić), to wartość null. I podaje twój NRE. Można przerwać ten oddzwonić spliting zachowanie konstruktora tak:

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 
    } 
+2

Dzięki. Masz rację. Ale właśnie to [Patrick] (http://stackoverflow.com/a/41937705/284240) już odpowiedział :) –

+2

@TimSchmelter Powiedziałem ci w komentarzu, próbowałem dowiedzieć się wystarczająco dużo C#, aby pokazać rozwiązanie. Jest to znany problem dla mnie w java. –