Singleton autorstwa Jona Skeeta

214
public sealed class Singleton
{
    Singleton() {}

    public static Singleton Instance
    {
        get
        {
            return Nested.instance;
        }
    }

    class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested() {}
        internal static readonly Singleton instance = new Singleton();
    }
}

Chciałbym wdrożyć wzór Jona Skeeta Singletona w mojej bieżącej aplikacji w języku C #.

Mam dwie wątpliwości co do kodu

  1. Jak można uzyskać dostęp do zewnętrznej klasy wewnątrz klasy zagnieżdżonej? mam na myśli

    internal static readonly Singleton instance = new Singleton();

    Czy coś nazywa się zamknięciem?

  2. Nie mogę zrozumieć tego komentarza

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit

    co sugeruje ten komentarz?

amutha
źródło
12
haha Myślałem, że powiedziałem, że to było trochę zmartwione lol ... okazał się innym Johnem Nolanem
John Antony Daniel Nolan
14
Dwie rzeczy są powszechnie akceptowane: słońce wschodzi ze wschodu, a Jon Skeet ma zawsze rację. Ale nie jestem jeszcze pewien pierwszego: P
akhil_mittal
2
@ thepirat000 - Gdyby był tylko uczestnikiem SO / Meta, mógłbym się nie zgodzić, ale ma on wystarczający wpływ na rzeczywisty świat programowania, który może być rzeczywiście legalny - jestem pewien, że ktoś go stworzył w tym czy innym momencie .
Code Jockey
8
Taksonomia tego pytania jest omawiana na meta .
BoltClock

Odpowiedzi:

359
  1. Nie, to nie ma nic wspólnego z zamknięciami. Zagnieżdżona klasa ma dostęp do prywatnych członków swojej zewnętrznej klasy, w tym prywatnego konstruktora tutaj.

  2. Przeczytaj mój artykuł na temat beforefieldinit . Możesz chcieć lub nie chcieć statycznego konstruktora no-op - zależy to od tego, jakie lenistwo gwarantuje, czego potrzebujesz. Należy pamiętać, że .NET 4 zmienia nieco semantykę inicjowania typu rzeczywistego (nadal w specyfikacji, ale bardziej leniwe niż wcześniej).

Czy naprawdę potrzebujesz tego wzoru? Czy na pewno nie możesz uciec od:

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();
    public static Singleton Instance { get { return instance; } }

    static Singleton() {}
    private Singleton() {}
}
Jon Skeet
źródło
12
@Anindya: Nie, w porządku. Możesz jednak wysłać JetBrains na skargę :)
Jon Skeet
2
@JonSkeet, właśnie zwróciłem się do JetBrains z obawą (# RSRP-274373). Zobaczmy, co mogą wymyślić. :)
Anindya Chatterjee
3
@Moons: Ty nie. Singleton żyje przez czas AppDomain.
Jon Skeet
2
@JonSkeet Jakiś powód, aby nie używać Lazy<T>, abyś nie musiał deklarować statycznego konstruktora magicznego BeforeFieldInitefektu ubocznego?
Ed T
3
FieldBeforeInitjest MahaBharataodMicrosoft
Amit Kumar Ghosh
49

Odnośnie do pytania (1): Odpowiedź Jona jest poprawna, ponieważ domyślnie zaznacza klasę „zagnieżdżoną” jako prywatną, nie podając jej do wiadomości publicznej ani wewnętrznej :-). Równie dobrze możesz to zrobić, dodając „prywatny”:

    private class Nested

Odnośnie pytania (2): w zasadzie, o czym jest post przedinicjalizacją i typem inicjalizacji , to to, że jeśli nie masz statycznego konstruktora, środowisko wykonawcze może go zainicjować w dowolnym momencie (ale przed użyciem). Jeśli masz konstruktora statycznego, kod w konstruktorze statycznym może zainicjować pola, co oznacza, że ​​środowisko wykonawcze może zainicjować pole tylko wtedy, gdy poprosisz o typ.

Jeśli więc nie chcesz, aby środowisko wykonawcze inicjowało pola „proaktywnie” przed ich użyciem, dodaj konstruktor statyczny.

Tak czy inaczej, jeśli implementujesz singletony, albo chcesz, aby inicjalizowało je możliwie jak najbardziej leniwie, a nie wtedy, gdy środowisko wykonawcze uzna, że ​​powinno zainicjować zmienną - lub zapewne nie obchodzi cię to. Z twojego pytania przypuszczam, że chcesz je jak najszybciej.

To spotyka się z postem Jona na temat singletonów , który IMO jest głównym tematem tego pytania. Aha i wątpliwości :-)

Chciałbym zaznaczyć, że jego singleton # 3, który zaznaczył jako „zły”, jest w rzeczywistości poprawny (ponieważ blokada automatycznie implikuje barierę pamięci przy wyjściu ). Powinien także być szybszy niż singleton # 2, jeśli używasz instancji więcej niż jeden raz (co jest mniej więcej celem singletonu :-)). Tak więc, jeśli naprawdę potrzebujesz leniwej implementacji singletona, prawdopodobnie wybrałbym tę - z prostych powodów, dla których (1) jest bardzo jasne dla wszystkich, którzy czytają Twój kod, co się dzieje i (2) wiesz, co się stanie z wyjątkami.

Jeśli zastanawiasz się: nigdy nie użyłbym singletona # 6, ponieważ może to z łatwością prowadzić do impasu i nieoczekiwanego zachowania z wyjątkami. Aby uzyskać szczegółowe informacje, patrz: tryb blokowania leniwego , w szczególności ExecutionAndPublication.

atlaste
źródło
62
Regarding question (1): The answer from Jon is correct ...Jon Skeet ma zawsze rację ....
Noctis
72
Dodatkowe punkty za próbę odpowiedzi na pytanie Jona Skeeta, na które odpowiedział już Jon Skeet.
valdetero
8
@valdetero Hahaha. To ... hahaha +1
akinuri