2009-04-27 17 views
5

Voglio avere un Singleton che verrà istanziato automaticamente all'avvio del programma.Istruzioni: Auto Instantiate Singleton in C#

Quello che intendo per "istanza automatica" è che il codice nel Singleton dovrebbe istanziarsi automaticamente all'avvio del programma senza alcuna chiamata o dichiarazione da parte di altro codice.

quindi voglio qualcosa di simile a quanto segue per istanziare e scrivere "MySingleton crea un'istanza" all'avvio del programma (senza il codice principale di fare qualsiasi cosa) ...

static class MySingleton 
{ 
    private static MySingleton self = new MySingleton(); 

    protected MySingleton() 
    { 
     System.Console.WriteLine("MySingleton Instantiated"); 
    } 
} 

ma questa non funziona in quanto C# solo inizializzare i membri statici di una classe quando necessario, cioè quando si accede a/etc.

Quindi cosa devo fare? Può essere fatto?

Non l'ho fatto personalmente con C++ (non ho usato C++ per un po ') ma sono abbastanza sicuro che possa essere fatto in C++ ma non sono sicuro di C#.

Qualsiasi aiuto è apprezzato. Grazie.


Quello che sto davvero voglia di fare con questo è ... ci sarebbero molte di queste classi singleton (e più può essere aggiunto come passa il tempo), ognuno dei quali avrebbe ereditato da una comune (astratto) genitore di classe (alias. PClass).

Il PClass avrebbe un membro statico che è una collezione di PClasses ... e un costruttore per aggiungere stesso per la raccolta ...

Poi, in teoria, tutti i single sarebbe automagicamente essere aggiunti alla collezione (dal momento in cui vengono istanziati viene chiamato il costruttore di base PClass e aggiunge il nuovo oggetto alla raccolta) ... quindi la raccolta può essere utilizzata senza sapere nulla su quali classi child (singleton) sono state implementate e classi new child (singleton) può essere aggiunto in qualsiasi momento senza dover modificare alcun altro codice.

Purtroppo non riesco a far sì che i bambini (singleton) si istanzino ... rovinando il mio piccolo piano, dando luogo a questo post.

Spero di averlo spiegato abbastanza bene.


PS. Sì, mi rendo conto che ci sono cattivi sentimenti nei confronti di Singletons e del loro uso ... ma a volte sono utili, e anche se Satana stesso avesse creato Singleton, mi piacerebbe ancora sapere se il mio problema può essere raggiunto in C#. Grazie gentilmente a tutti voi.

+2

Satan è un Singleton. –

risposta

6

L'approccio CIO citato da Chris è probabilmente la migliore, ma in mancanza di questo la soluzione "migliore" mi viene in mente è quello di fare qualcosa di strano con la riflessione e gli attributi lungo le linee di:

public class InitOnLoad : Attribute 
{ 
    public static void Initialise() 
    { 
     // get a list of types which are marked with the InitOnLoad attribute 
     var types = 
      from t in AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()) 
      where t.GetCustomAttributes(typeof(InitOnLoad), false).Count() > 0 
      select t; 

     // process each type to force initialise it 
     foreach (var type in types) 
     { 
      // try to find a static field which is of the same type as the declaring class 
      var field = type.GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).Where(f => f.FieldType == type).FirstOrDefault(); 
      // evaluate the static field if found 
      if (field != null) field.GetValue(null); 
     } 
    } 
} 

[InitOnLoad] 
public class Foo 
{ 
    public static Foo x = new Foo(); 

    private Foo() 
    { 
     Console.WriteLine("Foo is automatically initialised"); 
    } 
} 

public class Bar 
{ 
    public static Bar x = new Bar(); 

    private Bar() 
    { 
     Console.WriteLine("Bar is only initialised as required"); 
    } 
} 

con un chiama a InitOnLoad.Initialise() aggiunto al tuo metodo principale.

È possibile eliminare l'attributo, ma ciò può causare l'inizializzazione di tipi non necessari e consumare inutilmente memoria (ad esempio Barra nel codice precedente).

Vale anche la pena notare che questo non gestirà i tipi contenuti in nessun assembly che vengono caricati dinamicamente, a meno che non si effettui un'altra chiamata a Inizializzazione, ma in base alla domanda ("istanziata automaticamente all'avvio del programma") che non sembra un problema per te.

+0

Dopo un aggiornamento alla domanda, sarebbe possibile sostituire l'attributo con un controllo del tipo di antenato. Il codice Initialise può quindi essere spostato su un costruttore statico nella classe base, non appena la classe base viene referenziata nell'applicazione causerebbe anche il caricamento di tutti gli antenati. – Richard

+0

Ho sentito parlare di Reflection ma non l'ho usato ... mi chiedevo se potesse essere usato per una cosa come questa ... Penso che questa sia la soluzione migliore, e il tuo commento di follow-up descrive fondamentalmente esattamente quello che volevo fare. Con Reflection penso che questo possa essere raggiunto. Grazie. – devlop

5

Mentre i moduli .NET possono in teoria (IIRC) reagire al carico del modulo ecc., Questo non è disponibile tramite C#. In alcuni framework (come ASP.NET) ci sono degli hook che puoi usare tramite la configurazione, come hackerarlo tramite un gestore o tramite global.asax.cs - tuttavia, per una normale app C# (console, winform, ecc.) Dovresti attivarlo manualmente. Ad esempio, viene richiamato un costruttore statico sulla classe che ospita il punto di ingresso Main.

Quindi: qual è il caso d'uso qui? Quando l'approccio di caricamento lento non dovrebbe essere OK?

+0

Ho modificato il post per aggiungere il mio caso d'uso. – devlop

2

Dubito che questo sia possibile senza fare nulla da Main. Anche aggiungere un static MySingleton() {} alla classe non garantisce la sua istanziazione se non lo si usa.

0

Non credo che ciò che si desidera sia possibile in .Net framework. In sostanza, si desidera qualcosa come il runtime per notificare i tipi o chiamare su di essi un metodo statico predefinito che l'applicazione sia caricata e in esecuzione o fornisca un meccanismo del genere. Pensa al sovraccarico. L'unica cosa che garantisce il runtime è chiamare automaticamente il metodo di immissione principale per il tuo programma. Questo è tutto. Qui puoi impostare le cose e inizializzarle, ma non c'è nulla di automatico. Stai ancora collegando in modo esplicito le cose e facendo chiamate di metodo.

+0

Sto iniziando a pensare che non sia possibile neanche ... ma dovrebbe essere! Sono abbastanza sicuro che possa essere fatto in C++, anche se in realtà non l'ho fatto ... e ovviamente C# non è C++ ma è fastidioso che questo tipo di cose non può essere fatto in C#. – devlop

+0

Dubito che sia persino possibile anche in C++. Ricordo che ci sono alcuni richiami standard in ogni dll che Windows chiama ogni volta che una dll viene caricata, scaricata, collegata, ecc. Come menzionato da Mark Gravell, ma anche in questo caso sarà necessario inizializzare esplicitamente i tuoi singleton. Inoltre, in .NET, non hai accesso a tali callback. Può essere una possibilità avere il proprio bootstrapper per ospitare il runtime .NET. Quindi puoi usare l'API di hosting CLR o qualche trucco per fare ciò che vuoi ma perché? Penso che dovresti ripensare al tuo design e alla necessità di questo requisito. –

1

Stai praticamente chiedendo al framework .NET di chiamare qualche funzione ogni volta che carica un assembly. Quella funzione sarebbe il registrar delle istanze di PClass.

Non c'è DllMain in C#. Puoi simularlo avendo un attributo a livello di assembly e un codice nel tuo metodo principale, che ascolta ogni volta che viene caricato un assembly. L'attributo può avere un punto di ingresso "DllMain" specificato o indicare le classi ereditate da PCIass.

1

Tangenzialmente, permettetemi di precisare che questo non è il modello singleton in quanto è normalmente implementato. Ecco il normale (semplificato) implementazione in C# (che non includono cose come thread-sicurezza per brevità):

public sealed class Singleton 
{ 
static readonly Singleton _instance = new Singleton(); 

// private ctor 
Singleton() {} 

public static Singleton Instance 
{ 
    get { return _instance; } 
} 
} 

Questa riga dal codice non deve anche compilare!

protected MySingleton() 

Detto questo, sono d'accordo con il ragazzo che ha chiesto informazioni sul suo caso d'uso. Sarebbe bello saperlo. =)

+0

Ya, in genere le persone hanno un accesso di istanza come si dimostra, ma il mio problema è che non voglio che nessun altro codice acceda direttamente al singleton ... Voglio solo che istanzia da solo ... quindi non c'è bisogno di un accessorio. Inoltre ho modificato la mia domanda per aggiungere la spiegazione del caso d'uso. – devlop

+0

BTW, cosa c'è di sbagliato con un costruttore protetto. Compila bene. – devlop

+0

Si dovrebbe ottenere un errore del compilatore ("Le classi statiche non possono avere costruttori di istanze") quando si aggiunge un ctor (di qualsiasi livello di accesso) su una classe statica. – Garrett

4

In base a ciò che si sta tentando di fare, vorrei abbandonare l'idea di un vero Singleton e utilizzare invece una libreria IoC.

Controllare StructureMap, Castle Windsor, Ninject e/o Autofac.

Ciò ti consentirà di creare classi come un singleton, tramite la libreria IoC, di averne quante ne vuoi, ma è solo una semplice vecchia classe.

I singleton hanno un problema in quanto compromettono la testabilità (tramite test delle unità) dell'applicazione.

Basta fare una ricerca su google "Singleton Considerato Nocivo" e vedrete molti più riferimenti.

In alternativa, è anche possibile utilizzare un modello di fabbrica Class Factory/Method semplice.

+0

Non ho familiarità con le librerie IoC ... e per i miei bisogni sembra più complicato di quello che voglio? Attualmente come soluzione sto praticamente facendo un metodo di fabbrica. Ma non è così bello. – devlop

+0

Un IoC non è poi così complicato. Il vantaggio aggiunto è che anche un IoC può aiutare il resto della tua applicazione a causa dell'iniezione automatica delle dipendenze. Anche se non lo usi per questo problema, vale la pena esaminare IoC. –

0

"Il PClass avrebbe un membro statico che è una raccolta di PClasses ..."

suona come qualcosa che potrebbe essere fatto facilmente con la riflessione. Non hai bisogno del codice che viene eseguito all'avvio; puoi semplicemente costruire una lista di queste classi ogni volta che ne hai bisogno.

Ecco un esempio che caricherà l'elenco una volta la prima volta che si legge la proprietà Istanze, verrà visualizzato in tutti gli assembly caricati in quel momento (è possibile semplificarlo un po 'se si desidera solo cercare nello stesso assieme come PClass, ma la tua domanda non ha specificato quali sono gli assembly che si desidera esaminare) e aggiungerà all'elenco una singola istanza di qualsiasi discendente di PClass (ma non di PClass stesso). Ogni discendente di PClass avrà bisogno di un costruttore senza parametri, ma non sarà necessario alcun costrutto di classe.

public class PClass 
{ 
    private static List<PClass> m_instances; 
    public static IList<PClass> Instances 
    { 
     get 
     { 
      if (m_instances == null) 
       m_instances = LoadInstanceList(); 
      return m_instances; 
     } 
    } 
    private static List<PClass> LoadInstanceList() 
    { 
     foreach (var assembly in AppDomain.GetAssemblies()) 
     { 
      foreach (var type in assembly.GetTypes()) 
      { 
       if (type.IsAssignableTo(typeof(PClass)) && type != typeof(PClass)) 
        m_instances.Add(Activator.CreateInstance(type)); 
      } 
     } 
    } 
} 
+0

Grazie, Richard è arrivato prima con le cose di Reflection ... quindi gli ha dato il Check. Ma grazie per la risposta comunque. Non puoi fare controlli multipli qui puoi? – devlop

+0

Activator.CreateInstance richiede (di default almeno) un costruttore pubblico predefinito. L'esempio singleton fornito non ne ha fornito uno, questo è il motivo per cui ho usato reflection per trovare un campo dello stesso tipo della classe dichiarante e quindi ottenere il valore di quel campo. – Richard