Pregunta

En otras palabras, ¿es seguro este subproceso de implementación Singleton?

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

    static Singleton()
    {
        instance = new Singleton();
    }

    public static Singleton Instance
    {
        get { return instance; }
    }
}
¿Fue útil?

Solución

Se garantiza que los constructores estáticos se ejecutarán solo una vez por dominio de aplicación, antes de que se creen instancias de una clase o se acceda a miembros estáticos. http://msdn.microsoft.com/en-us/library/aa645612.aspx

La implementación que se muestra es segura para subprocesos para la construcción inicial, es decir, no se requieren bloqueos ni pruebas nulas para construir el objeto Singleton.Sin embargo, esto no significa que se sincronizará cualquier uso de la instancia.Hay varias formas de hacer esto;He mostrado uno a continuación.

public class Singleton
{
    private static Singleton instance;
    // Added a static mutex for synchronising use of instance.
    private static System.Threading.Mutex mutex;
    private Singleton() { }
    static Singleton()
    {
        instance = new Singleton();
        mutex = new System.Threading.Mutex();
    }

    public static Singleton Acquire()
    {
        mutex.WaitOne();
        return instance;
    }

    // Each call to Acquire() requires a call to Release()
    public static void Release()
    {
        mutex.ReleaseMutex();
    }
}

Otros consejos

Si bien todas estas respuestas dan la misma respuesta general, hay una advertencia.

Recuerde que todas las posibles derivaciones de una clase genérica se compilan como tipos individuales.Por tanto, tenga cuidado al implementar constructores estáticos para tipos genéricos.

class MyObject<T>
{
    static MyObject() 
    {
       //this code will get executed for each T.
    }
}

EDITAR:

Aquí está la demostración:

static void Main(string[] args)
{
    var obj = new Foo<object>();
    var obj2 = new Foo<string>();
}

public class Foo<T>
{
    static Foo()
    {
         System.Diagnostics.Debug.WriteLine(String.Format("Hit {0}", typeof(T).ToString()));        
    }
}

En la consola:

Hit System.Object
Hit System.String

Usar un constructor estático en realidad es a salvo de amenazas.Se garantiza que el constructor estático se ejecutará solo una vez.

De la especificación del lenguaje C# http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx:

El constructor estático de una clase se ejecuta como máximo una vez en un dominio de aplicación determinado.La ejecución de un constructor estático se activa cuando ocurre el primero de los siguientes eventos dentro de un dominio de aplicación:

  • Se crea una instancia de la clase.
  • Se hace referencia a cualquiera de los miembros estáticos de la clase.

Entonces sí, puede confiar en que se creará una instancia correcta de su singleton.

Zooba señaló de manera excelente (¡y 15 segundos antes que yo también!) que el constructor estático no garantizará el acceso compartido seguro para subprocesos al singleton.Eso habrá que manejarlo de otra manera.

Aquí está la versión de Cliffnotes de la página de MSDN anterior en c# singleton:

Utiliza el siguiente patrón, siempre, no te puedes equivocar:

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

   private Singleton(){}

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

Más allá de las características obvias de singleton, le ofrece estas dos cosas de forma gratuita (con respecto a singleton en c++):

  1. construcción perezosa (o ninguna construcción si nunca fue llamada)
  2. sincronización

Se garantiza que los constructores estáticos se activarán solo una vez por dominio de aplicación, por lo que su enfoque debería ser correcto.Sin embargo, funcionalmente no es diferente de la versión en línea más concisa:

private static readonly Singleton instance = new Singleton();

La seguridad de los subprocesos es un problema mayor cuando se inicializan cosas de forma perezosa.

El Especificación de infraestructura de lenguaje común garantiza que "un inicializador de tipo se ejecutará exactamente una vez para cualquier tipo dado, a menos que lo llame explícitamente el código de usuario". (Sección 9.5.3.1.) Entonces, a menos que tenga un poco de IL Whacky en la llamada suelta Singleton ::. CCTOR directamente (poco probable) Su constructor estático se ejecutará exactamente una vez antes de que se use el tipo Singleton, solo se creará una instancia de singleton, Y su propiedad de instancia es segura de hilo.

Tenga en cuenta que si el constructor de Singleton accede a la propiedad de Instancia (incluso indirectamente), entonces la propiedad de Instancia será nula.Lo mejor que puede hacer es detectar cuándo sucede esto y generar una excepción, verificando que la instancia no sea nula en el descriptor de acceso a la propiedad.Una vez que su constructor estático complete, la propiedad de Instancia no será nula.

Como La respuesta de Zoomba señala que deberá hacer que el acceso a Singleton sea seguro desde múltiples subprocesos o implementar un mecanismo de bloqueo para usar la instancia singleton.

El constructor estático finalizar correr antes cualquier hilo puede acceder a la clase.

    private class InitializerTest
    {
        static private int _x;
        static public string Status()
        {
            return "_x = " + _x;
        }
        static InitializerTest()
        {
            System.Diagnostics.Debug.WriteLine("InitializerTest() starting.");
            _x = 1;
            Thread.Sleep(3000);
            _x = 2;
            System.Diagnostics.Debug.WriteLine("InitializerTest() finished.");
        }
    }

    private void ClassInitializerInThread()
    {
        System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() starting.");
        string status = InitializerTest.Status();
        System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() status = " + status);
    }

    private void classInitializerButton_Click(object sender, EventArgs e)
    {
        new Thread(ClassInitializerInThread).Start();
        new Thread(ClassInitializerInThread).Start();
        new Thread(ClassInitializerInThread).Start();
    }

El código anterior produjo los resultados a continuación.

10: ClassInitializerInThread() starting.
11: ClassInitializerInThread() starting.
12: ClassInitializerInThread() starting.
InitializerTest() starting.
InitializerTest() finished.
11: ClassInitializerInThread() status = _x = 2
The thread 0x2650 has exited with code 0 (0x0).
10: ClassInitializerInThread() status = _x = 2
The thread 0x1f50 has exited with code 0 (0x0).
12: ClassInitializerInThread() status = _x = 2
The thread 0x73c has exited with code 0 (0x0).

Aunque el constructor estático tardó mucho en ejecutarse, los otros subprocesos se detuvieron y esperaron.Todos los hilos leen el valor de _x establecido en la parte inferior del constructor estático.

Solo para ser pedante, pero no existe un constructor estático, sino inicializadores de tipo estático, aquí hay un pequeño Demostración de la dependencia del constructor estático cíclico que ilustra este punto.

Se garantiza que el constructor estático es seguro para subprocesos.Además, consulte la discusión sobre Singleton en DeveloperZen:http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/

Aunque otras respuestas son en su mayoría correctas, hay otra advertencia con los constructores estáticos.

Según sección II.10.5.3.3 Carreras y puntos muertos del Infraestructura de lenguaje común ECMA-335

La inicialización de tipo solo no creará un punto muerto a menos que algún código llamado desde un tipo inicializador (directa o indirectamente) invoque explícitamente las operaciones de bloqueo.

El siguiente código produce un punto muerto

using System.Threading;
class MyClass
{
    static void Main() { /* Won’t run... the static constructor deadlocks */  }

    static MyClass()
    {
        Thread thread = new Thread(arg => { });
        thread.Start();
        thread.Join();
    }
}

El autor original es Igor Ostrovsky, mira su publicación. aquí.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top