簡體   English   中英

關於單例的非靜態成員的問題(C#)

[英]Question about non-static members of a singleton (C#)

我有一個關於單身人士的問題,我認為我知道答案......但每次情況彈出時我都會稍微猜測一下自己,所以我想知道具體的答案。

假設我有兩個類設置如此...

 public class ClassA
 {
     private static ClassA _classA;

     public static ClassA Instance { get { return _classA ?? LoadClassA(); } }

     private ClassA(){}

     public static ClassA LoadClassA()
     {
         _classA = new ClassA();
         return _classA;
     }

     private ClassB _classB = new ClassB();

     public ClassB ClassB { get { return _classB; } set { _classB = value; } }
 }


 public class ClassB
 {
 }

我的問題很簡單。

我想知道如果我訪問ClassA的單例,_classB字段是否也被視為靜態? 即使我沒有將_classB聲明為靜態成員。

我總是基本上猜到_classB它被視為靜態(一個內存位置),但我想知道肯定。 我錯了嗎? 每次從singleton ClassA訪問時都會為_classB創建一個新對象...即使內存中只有一個ClassA? 或者是因為我在聲明中新建了_classB,導致只有一個實例?

提前謝謝,-Matt

創建單例時,您將創建一個非靜態類型的靜態實例。

在這種情況下,您的類型(A類)包含對另一種類型(B類)的引用。 靜態實例將保存對B類對象的單個實例的單個引用。 從技術上講,它不是“靜態的”,但由於它的根源是靜態對象(類A實例),它的行為就像一個靜態變量。 您將始終擁有一個且僅有一個B類對象(由A類實例指向)。 您永遠不會在A類中創建多個B類實例

但是,沒有什么能阻止在其他地方生成第二個B類實例 - 這將是一個不同的實例。

_classB是ClassA的實例(非靜態)成員。 ClassA的每個實例都有一個_classB實例(給定你編寫的字段初始值設定項)。 因此,如果您使用Singleton模式訪問ClassA,因此總是(最多)加載一個ClassA實例,您將始終(最多)通過ClassA加載一個ClassB實例。

現在,由於ClassB有一個公共默認構造函數,所以遠在其他地方可能會自己創建實例。 如果這是一個問題,請考慮將ClassB類設為私有。 此外,由於ClassA具有公共默認構造函數,因此沒有什么能阻止人們創建任意數量的實例。 您可以將no-arg構造函數設為私有:

private ClassA() {}

單例模式確保始終只能ClassA的單例實例訪問ClassB一個實例。 單例模式的重點在於它保證在任何時候只有一個 ClassA 實例可用,因此只有一個對_classB引用(盡管因為ClassA是可變的,所以這個引用可以改變)。

但請注意, ClassB范圍仍然是實例級而不是靜態級。 編譯器永遠不會做任何奇怪的事情,以至於使用與您指示的不同的范圍說明符。 無論您是否使用單例,您仍必須通過實例訪問ClassB的引用。

通過這樣的聲明:

private ClassB _classB = new ClassB();

每當調用ClassA的構造函數時,您都會將_classB實例化為ClassB的新實例。

使用單例模式,調用ClassA的構造函數的唯一方法是使用靜態方法(在您的情況下通過Instance屬性),這有效地保證只創建一個ClassA。

這樣可以確保_classB只會被新建一次,但它是非靜態的。 但是,如果有人將ClassA更改為將來不再是單例,那么您將開始創建多個ClassB實例。 如果_classB真的是靜態的,那么情況就不是這樣了。

如定義的,ClassA違反了單身人士的定義。 想象一下兩個線程同時調用靜態Instance屬性。 由於訪問不同步,您可以使用兩個不同的ClassA實例,從而獲得兩個不同的ClassB實例。

  1. Thread1調用Instance屬性,當_classA為null時,它進入LoadClassA方法
  2. Thread2調用Instance屬性,因為_classA仍為null,它進入LoadClassA方法
  3. Thread1和Thread2獲得ClassA的兩個不同實例

如果只需要一個實例,為什么不將ClassB標記為靜態。

在我看來,編寫清晰的代碼以向您展示完整的意圖總是更好。 這樣,下一個處理代碼的人就不必玩猜謎游戲來確定你想做什么。

因為ClassA是單例,所以在您的應用程序中只有一個實例。 在該類實例中,有一個存儲ClassB變量的成員。 初始化此變量后,它是一個實例變量,但由於您只有1個ClassA實例,因此您將始終獲得相同的ClassB實例。

另外,我建議不要創建內部實例,因為它不是線程安全的。

private static ClassA _instance = new ClassA();

private ClassA() {}

public static ClassA Insance { get { return _instance; } }

通過不延遲加載和立即初始化,您可以確保線程安全。

_classB不是靜態的; ClassA的實例包含對_classB實例的引用; 如果ClassA的實例是靜態的(因為它在單例實例中),那么它仍將包含對_classB的引用,但ClassA的新實例將包含對(可能)_classB的不同實例的DIFFERENT引用。 因此,如果您引用相同的ClassA實例(通過單例模式),您仍將獲得相同的_classB實例; 但是如果你創建一個新的ClassA實例,你會得到一個不同的_classB實例,這就是為什么_classB不是靜態的。

令我驚訝的是,沒有人指出ClassB是具有公共二傳手的ClassA的公共財產。 因此,沒有什么能阻止任何人寫下這樣的東西:

ClassA.Instance.ClassB = new ClassB();

這將丟棄的實例ClassB當時創建ClassA的孤實例被創建,並會用新創建的實例替換它ClassB

一個相關的場景是,只要_classB在派生類中是可設置的(例如,如果ClassB的setter受到保護),就不能保證只有ClassB的單個實例。

我想編寫這種代碼的正確方法是將ClassAClassB都實現為單例,並使ClassB成為ClassA的內部類(否則你將有2個不相關的類ClassAClassB ,通過ClassA.Instance.ClassB可以引用ClassB ClassA.Instance.ClassB ,它(在那種情況下)與ClassB.Instance沒有什么不同。

如果ClassB只能在ClassA實例化,那么可能在派生類中可以有其他ClassB實例化實例化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM