2009-04-27 25 views
5

Quiero tener un Singleton que se creará automáticamente en el inicio del programa.Cómo: Instancia automática de Singleton en C#

Lo que quiero decir con "instancia automática" es que el código en Singleton debe crearse una instancia en el inicio del programa sin ninguna llamada o declaración por otro código.

por lo que quiero algo como lo siguiente para crear una instancia y escribir "MySingleton crea la instancia" al inicio (sin el código principal hacer nada) ...

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

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

excepto que esta no funciona ya que C# se solo inicialice los miembros estáticos de una clase cuando sea necesario, es decir, cuando se acceda a ellos/etc.

Entonces, ¿qué debo hacer? ¿Se puede hacer esto?

No he hecho esto personalmente con C++ (no he usado C++ por un tiempo) pero estoy bastante seguro de que se puede hacer en C++ pero no estoy seguro de C#.

Cualquier ayuda es apreciada. Gracias.


Lo que estoy realmente queriendo hacer con esto es ... Habría muchas de estas clases únicos (y más se pueden añadir como pasa el tiempo), todo lo cual heredar de una común (resumen) clase de padres (también conocido como PClass).

El pClass tendría un miembro estático que es una colección de PClasses ... y un constructor para sumarse a la colección ...

Luego, en teoría, todos los embarazos únicos automágicamente se añadiría a la colección (ya que cuando se crean instancias se llama al constructor PClass base y agrega el nuevo objeto a la colección) ... entonces la colección se puede usar sin saber nada sobre qué clases de elementos secundarios (singleton) se han implementado, y nuevas clases secundarias (singleton) se puede agregar en cualquier momento sin tener que cambiar ningún otro código.

Desafortunadamente no puedo hacer que los niños (únicos) se creen ellos mismos ... arruinando mi pequeño plan, lo que resulta en esta publicación.

Espero que lo haya explicado lo suficientemente bien.


PS. Sí, me doy cuenta de que hay malos sentimientos en torno a Singletons y su uso ... pero a veces son útiles, e incluso si el mismo Satanás creara Singletons, me gustaría saber si mi problema puede lograrse en C#. Gracias amablemente a todos ustedes.

+2

Satan es un Singleton. –

Respuesta

6

El enfoque COI mencionado por Chris es probablemente la mejor, pero fallando que la "mejor" solución que se me ocurre es hacer algo funky con la reflexión y la atribuye a lo largo de las líneas de:

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 una llame a InitOnLoad.Initialise() agregado a su método principal.

Podría deshacerse del atributo, pero esto puede provocar la inicialización de tipos innecesarios y el consumo innecesario de memoria (como Bar en el código anterior).

También vale la pena señalar que esto no manejará los tipos contenidos en ensamblados que se cargan dinámicamente, a menos que realice otra llamada a Inicializar, pero en función de su pregunta ("autoinstanciada en el inicio del programa") que no suena como un problema para ti.

+0

Después de una actualización de la pregunta, sería posible reemplazar el atributo con una verificación de tipo de antepasado. El código Initialise se puede mover a un constructor estático en la clase base, tan pronto como se haga referencia a la clase base en la aplicación, también se cargarán todos los antepasados. – Richard

+0

He oído hablar de Reflection pero no lo he usado ... me preguntaba si podría usarse para algo así ... Creo que esto suena como la mejor solución, y su comentario de seguimiento describe básicamente exactamente lo que quería hacer Con Reflection, creo que esto se puede lograr. Gracias. – devlop

5

Mientras que los módulos .NET pueden en teoría (IIRC) reaccionar a la carga del módulo, etc., esto no está disponible a través de C#. En algunos marcos (como ASP.NET) hay enganches que puede usar a través de la configuración, como hackearlos a través de un controlador oa través de global.asax.cs; sin embargo, para una aplicación C# normal (consola, winform, etc.) tendría que activarlo manualmente Por ejemplo, se invocará un constructor estático en la clase que aloja su punto de entrada Main.

Entonces, ¿cuál es el caso de uso aquí? ¿Cuándo no estaría bien el enfoque de carga diferida?

+0

Edité la publicación para agregar mi caso de uso. – devlop

2

Dudo que esto sea posible sin hacer nada desde Main. Incluso agregar un static MySingleton() {} a la clase no garantiza su creación de instancias si no lo usa.

0

No creo que lo que desea sea posible en .Net framework. Básicamente, desea algo como el tiempo de ejecución para notificar a los tipos o llamar a algún método estático predefinido sobre ellos que la aplicación está cargada y ejecutándose o proporcionar un mecanismo como ese. Piensa en los gastos generales. Lo único que asegura el tiempo de ejecución es llamar automáticamente al método de entrada principal para su programa. Eso es. Allí puede configurar las cosas e inicializarlas, pero no hay nada automático al respecto. Todavía estás conectando cosas explícitamente y haciendo llamadas a métodos.

+0

Estoy empezando a pensar que tampoco es posible ... ¡pero debería serlo! Estoy bastante seguro de que se puede hacer en C++, aunque en realidad no lo he hecho ... y obviamente C# no es C++, pero es molesto que este tipo de cosas no se pueda hacer en C#. – devlop

+0

También dudo que sea posible en C++. Recuerdo que hay algunas devoluciones de llamada estándar en cada dll que Windows llama cada vez que se carga un archivo dll, se descarga, se conecta un hilo, etc., como mencionó Mark Gravell, pero incluso entonces tendrá que inicializar explícitamente sus singletons. Además, en .NET, no tienes acceso a esas devoluciones de llamadas. Quizás una posibilidad sea tener tu propio programa de arranque para alojar el tiempo de ejecución de .NET. Entonces puedes usar las aplicaciones de CLR hosting o algún truco para hacer lo que quieras pero ¿por qué? Creo que deberías reconsiderar tu diseño y la necesidad de este requisito. –

1

Básicamente, le pide al .NET Framework que invoque alguna función cada vez que carga un ensamblaje. Esa función sería el registrador de instancias de PClass.

No hay DllMain en C#. Puede simular esto teniendo un atributo de nivel de ensamblaje y algún código en su método principal, que escucha cada vez que se carga un ensamblaje. El atributo puede tener un punto de entrada "DllMain" especificado o apuntar a sus clases PCIass heredadas.

1

Tangencialmente, permítanme señalar que este no es realmente el patrón singleton ya que normalmente se implementa. Aquí está la (simplificado) normal ejecución en C# (sin incluir cosas como hilos de seguridad por razones de brevedad):

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

// private ctor 
Singleton() {} 

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

Esta línea de su código no debería ni siquiera compilar!

protected MySingleton() 

Habiendo dicho todo eso, estoy de acuerdo con el tipo que ha preguntado sobre su caso de uso. Eso sería bueno saberlo. =)

+0

Ya, normalmente las personas tienen un acceso de instancia como usted lo demuestra, pero mi problema es que no quiero que ningún otro código acceda directamente al singleton ... Solo quiero que haga una instancia por sí mismo ... así que no hay necesidad de un accesorio. También edité mi pregunta para agregar la explicación del caso de uso. – devlop

+0

Por cierto, qué pasa con un constructor protegido. Compila bien. – devlop

+0

Debería obtener un error de compilación ("Las clases estáticas no pueden tener constructores de instancias") cuando agrega un ctor (de cualquier nivel de acceso) en una clase estática. – Garrett

4

Según lo que intenta hacer, dejaría de lado la idea de un Singleton verdadero, y usaría una biblioteca IoC en su lugar.

Echa un vistazo a StructureMap, Castle Windsor, Ninject y/o Autofac.

Esto le permitirá crear clases como singleton, a través de la biblioteca de IoC, tiene tantas como desee, pero es simplemente una vieja clase simple.

Los singleton tienen un problema porque realmente arruinan la capacidad de prueba (a través de la prueba unitaria) de su aplicación.

Haga una búsqueda en google en "Singleton Considered Harmful" y verá muchas más referencias.

Alternativamente, también puede usar un patrón de fábrica simple de Class Factory/Method.

+0

No estoy familiarizado con las bibliotecas de IoC ... ¿y para mis necesidades parece más complicado de lo que quiero? Actualmente como una solución, básicamente estoy haciendo un método de fábrica. Pero no es tan bueno. – devlop

+0

Un IoC no es tan complicado, de verdad. La ventaja adicional es que un IoC también puede ayudar al resto de su aplicación debido a la Inyección de Dependencia automática. Incluso si no lo usa para este problema, vale la pena investigar el IoC. –

0

"El pClass tendrían un miembro estático que es una colección de PClasses ..."

suena como algo que podría hacerse fácilmente con la reflexión. No necesita código que se ejecute al inicio; simplemente puede crear una lista de estas clases siempre que lo necesite.

Aquí hay un ejemplo que cargará la lista una vez cuando lea la propiedad Instancias, se verá en todos los ensamblados que estén cargados en ese momento (podría simplificarlo un poco si solo desea buscar en el mismo ensamblado que PClass, pero su pregunta no especificó en qué ensamblajes deseaba mirar) y agregará una sola instancia de cualquier descendiente de PClass (pero no de PClass) a la lista. Cada descendiente de PClass necesitará un constructor sin parámetros, pero no necesitará ningún constructor de clase.

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

Gracias, Richard llegó primero con las cosas de Reflexión ... así que le dio el cheque. Pero gracias por la respuesta de todos modos. No puedes hacer múltiples controles aquí ¿verdad? – devlop

+0

Activator.CreateInstance requiere (al menos de forma predeterminada) un constructor público predeterminado. El singleton de ejemplo dado no proporcionaba uno, esta es la razón por la que usé la reflexión para encontrar un campo del mismo tipo que la clase declarante y luego obtener el valor de ese campo. – Richard

Cuestiones relacionadas