2017-01-30 16 views
5

Theo this question nó phải được đảm bảo rằng các trường tĩnh mà tôi sử dụng được khởi tạo:NullReferenceException từ singleton tĩnh inline khởi

10.4.5.1 tĩnh lĩnh vực khởi:

Trường tĩnh initializers biến của một lớp tương ứng với a chuỗi các bài tập được thực hiện theo thứ tự văn bản trong mà chúng xuất hiện trong tuyên bố lớp học. Nếu một hàm dựng tĩnh (Phần 10.11) tồn tại trong lớp, việc thực thi trường tĩnh trình khởi tạo xảy ra ngay lập tức trước khi thực hiện hàm tạo tĩnh đó. Nếu không, bộ khởi tạo trường tĩnh được thực hiện tại thời gian thực hiện phụ thuộc trước khi sử dụng lần đầu tiên của trường tĩnh của lớp đó.

Tôi đã gặp phải trường hợp lạ khi điều này có vẻ không đúng. Tôi có hai lớp có sự phụ thuộc vòng tròn vào nhau và nơi một số NullReferenceException được ném.

tôi đã có thể tạo lại vấn đề này trong sau mẫu đơn giản, có một cái nhìn:

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

Vấn đề vẫn còn nếu bạn bỏ ghi chú các nhà thầu tĩnh như đề xuất trong question khác. Sử dụng mã này để có được một TypeInitializationException với NullRefernceException như InnerException trong Synchronize tại SessionManager.GetInstance().GetAllActiveSessions():

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

Console:

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

Tôi hiểu rằng có một số loại phụ thuộc vòng tròn ở đây (trong mã gốc không rõ ràng như vậy), nhưng tôi vẫn không hiểu tại sao mã không khởi tạo được những người độc thân. Cách tiếp cận tốt nhất cho trường hợp sử dụng này ngoài việc tránh phụ thuộc vòng tròn là gì?

+1

Thật kỳ lạ, tôi đã mong đợi sẽ thấy 'StackOverflowException'. Nó thường không phải là một ý tưởng tốt để làm bất cứ điều gì quá tinh vi trong quá trình xây dựng, đặc biệt là khi mỗi là một singleton và mỗi cuối cùng gọi khác trước khi hoặc đã hoàn thành hoàn thành khởi tạo. Có lẽ nhìn vào _ initialferred initialisation_? – MickyD

+3

Nếu bạn nhìn này: 'private SessionManager _instance = new SessionManager()', nó có hai bước quan trọng. 1.- Khởi tạo ('SessionManager()') và 2 asignation ('_instance = obj'). nếu bạn cố gắng sử dụng '_instance' trước khi asignation (như bạn làm), nó là null. Và nó ném NPE của bạn. –

Trả lời

5

Nhìn vào IL:

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

Ở đây bạn thấy rằng các cuộc gọi đến constructor tĩnh có hai bước. Nó khởi tạo một thể hiện mới, và sau đó nó gán nó. Điều đó có nghĩa là khi bạn thực hiện các cuộc gọi chéo mà phụ thuộc vào sự tồn tại của cá thể, bạn bị mắc kẹt. Nó vẫn đang ở giữa việc tạo ra thể hiện. Sau đó nó có thể được gọi.

Bạn có thể thoát khỏi điều này bằng cách tạo phương thức tĩnh Initialize, tức là cuộc gọi tức thì.

Hãy thử điều này:

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

    _instance.RecoverState(); 
} 

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

    _instance.Synchronize(); 
} 
+0

Xem mã trong bản cập nhật. Nếu bạn sử dụng hàm tạo tĩnh để phân tách sự khởi tạo từ khởi tạo, nó hoạt động. –

+0

Và về "gọi hàm dựng tĩnh", hàm tạo tĩnh bao gồm hai bước. Không có cuộc gọi có thể nhìn thấy bên cạnh đó từ CLR của khóa học. –

+0

Đây là những gì tôi có bây giờ: http://ideone.com/xzceqp –

1

Bạn đang gấp rút các bước và bạn có một số loại đệ quy xảy ra:

  1. SessionManager _instance = new SessionManager(); dòng này gọi một số phương pháp mà kết thúc bằng một cuộc gọi đến SessionManagerDatabase.GetInstance()

  2. Điều này cũng giống như vậy và kết thúc bằng một cuộc gọi trở lại SessionManager.GetInstance()

  3. Điều này gây ra sự cố vì yêu cầu giá trị hợp lệ được giữ trong biến _instance trong SessionManager, nhưng tại thời điểm đó bạn chưa thực sự hoàn thành chuỗi cuộc gọi phương thức để cung cấp giá trị phù hợp cho _instance do đó gây ra NullReferenceException.

+0

Cảm ơn câu trả lời. Bạn nói đúng, tôi nhận thấy nó rồi. Nhưng câu hỏi vẫn là cách tránh sự phụ thuộc vòng tròn này. +1 –

+0

@TimSchmelter Bạn có thể muốn suy nghĩ lại về thiết kế của mình. Việc sửa lỗi thông thường cho phụ thuộc vòng tròn là sử dụng các giao diện. Thật khó để đề xuất một cách tiếp cận khác với làm việc với các giao diện như tôi cho rằng bạn có rất nhiều mã mà bạn không hiển thị. – Deadzone

+0

tất nhiên bạn nói đúng, có nhiều cách khác để tránh điều này. Nhưng thực ra Patrick và David đã cho thấy cách sửa lỗi này một cách dễ dàng. Loại bỏ sự phụ thuộc từ hàm tạo và gọi phương thức tham chiếu lớp khác từ hàm tạo tĩnh. Vì chỉ có singleton được sử dụng nên không cần phải gọi nó từ constructor instance. –

1

Điều gì xảy ra trong ví dụ của bạn là trình tạo thể hiện được gọi trong quá trình khởi tạo trường tĩnh theo đặc điểm kỹ thuật. Nhưng hàm tạo không thành công với NullReferenceExeption vì nó cố gắng lấy tham chiếu đến _instance bằng lệnh GetInstance(). Xin lưu ý rằng _instance chưa được khởi tạo - quá trình khởi tạo đang diễn ra. Do đó constructor instance thất bại vì vấn đề ở trên và do đó nó không xây dựng/khởi tạo trường _instance. Vì vậy, một thời gian ngắn bạn nên cố gắng để có được _instance tĩnh từ constructor dụ của bạn.

+0

Cảm ơn câu trả lời. Bạn nói đúng, tôi nhận thấy nó rồi. Nhưng câu hỏi vẫn là cách tránh sự phụ thuộc vòng tròn này. Tôi không hiểu _ "cố gắng để có được _instance tĩnh từ constructor dụ của bạn" _ +1 –

+0

Xin lỗi tôi muốn nói rằng bạn không nên thử. Đối với các giải pháp tôi muốn đề nghị sử dụng Lazy thay vì singleton dựa trên một lĩnh vực tĩnh. The Lazy đảm bảo thực thi an toàn chủ đề đơn giản của hàm khởi tạo, nơi bạn có thể lấy tất cả thông tin phiên từ db. –

3

Nếu bạn nhìn này: private static SessionManager _instance = new SessionManager(), nó có hai bước quan trọng.

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

Nếu bạn cố gắng sử dụng _instance trước khi asignation (như bạn làm), nó không có giá trị. Và nó gieo NRE của bạn. Bạn có thể ngắt cuộc gọi lại này để chia nhỏ hành vi của hàm tạo như sau:

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

Cảm ơn. Bạn đúng. Nhưng đó chính xác là những gì [Patrick] (http://stackoverflow.com/a/41937705/284240) đã trả lời :) –

+2

@TimSchmelter Tôi đã nói với bạn trong một bình luận, tôi đã cố gắng học đủ C# để hiển thị một giải pháp. Đây là một vấn đề nổi tiếng đối với tôi trong java. –

Các vấn đề liên quan