2009-06-04 14 views
19

Estaba pensando en el tema clásico de la inicialización Singleton perezoso - todo el asunto de la ineficiencia de:¿Una implementación singleton obvia para .NET?

if (instance == null) 
{ 
    instance = new Foo(); 
} 
return instance; 

Cualquiera que sepa lo que es un Singleton es está familiarizado con el tema (sólo necesita el si de una vez). Es trivial pero irritante.

Por lo tanto, pensé en una solución alternativa, por lo menos para .NET (aunque debe trabajar en cualquier lugar que tiene algún equivalente a funcionar punteros:

public class Foo 
{ 
    private delegate Foo FooReturner(); 

    private static Foo innerFoo; 

    private static FooReturner fooReturnHandler = new FooReturner(InitialFooReturner); 

    public static Foo Instance 
    { 
     get 
     { 
      return fooReturnHandler(); 
     } 
    } 
    private static Foo InitialFooReturner() 
    { 
     innerFoo = new Foo(); 
     fooReturnHandler = new FooReturner(NewFooReturner); 
     return innerFoo; 
    } 

    private static Foo NewFooReturner() 
    { 
     return innerFoo; 
    } 

} 

En pocas palabras - la Instancia devuelve un método delegado. El delegado se establece inicialmente en un método que inicializa su instancia, luego cambia el delegado para que apunte a un método de devolución simple.

Ahora, me gusta pensar que no soy terrible en mi trabajo, pero no tengo pretensiones acerca de ser increíble. No he visto un ejemplo de este código en ninguna parte.

Ergo, llegué a la conclusión de que me falta algo. Algo importante. O bien, que todo el problema es demasiado trivial para molestarnos pensando que mucho o esto hace algo horrible que destruirá el universo. O fallo en la búsqueda y, por lo tanto, no he visto los cientos de desarrolladores que usan este método. Algo, de todos modos.

Tenía la esperanza de que las buenas personas aquí en Stack Overflow pudieran darme una pista sobre qué (dejando de lado la controversia sobre si uno debería usar un Singleton en absoluto).

EDITAR una aclaración:

Esto no es un código de actuación (aunque si el diseño se degrada de forma activa el rendimiento más allá del modelo tradicional, que sería interesante saber).

Fue escrito puramente como prueba de concepto, y estoy consciente de que no es seguro para subprocesos como debería ser. ¿Hay alguna razón por la cual NO podría hacerse segura para hilos por su propia naturaleza?

+0

"ineficiencia" - Me preocuparía más que en realidad no es seguro para subprocesos ;-p –

+0

Vuelva a editar ... pero agrega complejidad y no soluciona ningún problema ... ¿por qué elegiría esto solo con un cheque nulo? Si desea corregir la seguridad del hilo, lea la página de Jon ... –

+0

+1 "Hipótesis de destrucción del universo". : P Aparte de eso, buena pregunta y favorita para el registro. –

Respuesta

29

This is the canonical, thread safe, lazy Singleton pattern in C#:

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(); 
    } 
} 
+6

Y para los programadores de Java, el equivalente se denomina modismo de "clase de titularidad de inicialización diferida", descrito en el elemento 71 de Effective Java 2nd ed. :-) –

+0

Que sea perezoso al tipo MySingleton, pero no necesariamente es granular a la propiedad .Instance –

+0

Vea el artículo de Jon al que se hace referencia en la respuesta de Marc; el patrón "canónico" puede no ser tan vago como quisiéramos. – AnthonyWJones

1

toda la cuestión de la ineficiencia de: ...

Lo ineficiencia?

Estas instrucciones darán como resultado fragmentos extremadamente rápidos de código de ensamblaje. Estoy completamente seguro de que no hay nada que ganar tratando de "optimizar" esto. Incluso si se te ocurre algo más rápido, será a un costo de complejidad significativo.

A menos que tenga evidencia positiva de que este código está afectando su rendimiento, debe usar el enfoque más simple que resuelva su problema.

+0

aunque simple, el enfoque de null-check no es realmente seguro para subprocesos ... –

+0

Dicho esto, el enfoque que está mostrando, es conceptualmente un patrón bien conocido en uso durante muchos años para JIT (Just-In-Time) compilación, incluida la "IL" de .NET: cuando se encuentra una llamada de función, se hace para "señalar" a una llamada del compilador para el origen de la función. La primera vez que se llama, lo que se ejecuta es el compilador, que compila el fragmento en código máquina y reemplaza la referencia para apuntar al código recién compilado. Finalmente, se llama al nuevo código y el compilado sale del camino. JIT es un escenario furiosamente crítico para el rendimiento que justifica este tipo de complejidad. –

+0

@Mark Gravell:> aunque simple, el enfoque de null-check no es realmente seguro para subprocesos ... <- Correcto. Si se llamará al singleton desde múltiples hilos, debe usar el patrón de bloqueo verificado doblemente, que en realidad está garantizado para funcionar.NET –

4

¿Ha medido el rendimiento?

¿Cree que una llamada a función adicional es más barata que una if?

Estoy de acuerdo con los demás que el uso del ctor estático funciona bien para esta inicialización.Esto también eliminará la condición de carrera inherente que tienes desde .net garantiza que solo se llamarán constructores estáticos una vez.

+0

Acepto, una comprobación nula es definitivamente más rápida que una llamada a un método ... –

10

Para evitar tener que copiar el código único, que podría hacer que el tipo genérico, como por ejemplo:

public abstract class Singleton<T> 
    where T: class, new() 
{ 
    public static T Instance 
    { 
     get { return Nested.instance; } 
    } 

    private class Nested 
    { 
     // Explicit static constructor to tell C# compiler 
     // not to mark type as beforefieldinit 
     static Nested() { } 

     internal static readonly T instance = new T(); 
    } 
} 

public sealed class MyType : Singleton<MyType> 
{ 
} 

class Program 
{ 
    static void Main() 
    { 
     // two usage pattterns are possible: 
     Console.WriteLine(
      ReferenceEquals(
       Singleton<MyType>.Instance, 
       MyType.Instance 
      ) 
     ); 
     Console.ReadLine(); 
    } 
} 
+1

Esta solución genérica abstracta tiene un defecto. Se basa en el tipo derivado que expone públicamente a un constructor, lo que a su vez permitiría a cualquiera usarlo. Por lo general, ctor() tiene que hacerse privado para obligar al cliente a usar la propiedad MyType.Instance. La implementación de copiar y pegar de Imho es un pequeño precio a pagar en este caso. – Sherlock

+1

No lo llamaría un defecto, es una opción de diseño. –

0

Siempre lo uso regular (no perezoso) uno (usted podría nido es como el otro ejemplos): (requiere using System.Reflection;)

public class SingletonBase<T> where T : class 
{ 
    static SingletonBase() 
    { 
    } 

    public static readonly T Instance = 
     typeof(T).InvokeMember(typeof(T).Name, 
           BindingFlags.CreateInstance | 
           BindingFlags.Instance | 
           BindingFlags.Public | 
           BindingFlags.NonPublic, 
           null, null, null) as T; 
} 
+0

Agregaría la restricción new() para escribir el parámetro T. De lo contrario, esto generará errores de tiempo de ejecución. –

+0

No permito que mis singletons tengan un constructor público. –

+0

Puede publicar esto en http://stackoverflow.com/questions/100081/whats-a-good-threadsafe-singleton-generic-template-pattern-in-c es lo más cercano a una respuesta correcta que he visto. –

1

me acaban de comenzar a programar en C# (que viene de C++ es un soplo de aire fresco) y esto es lo que uso para aplicar patrones únicos.

public sealed class MyClass 
{  
    #region make singleton 

    static readonly Lazy<MyClass> _singleton = 
     new Lazy<MyClass>(() => new MyClass()); 

    public static MyClass Singleton 
    { 
     get { return _singleton.Value; } 
    } 

    private MyClass() { Initialize() }; 

    #endregion 

    Initialize() { /*TODO*/ }; 
} 
1

El mejor método que he encontrado de crear un producto único es la siguiente:

public class Singleton 
{ 
    static public Singleton Instance { get; } = new Singleton(); 
    private Singleton() { ... } 
} 

Es muy concisa, y lo más importante, es también seguro para subprocesos, que puede ser muy molesto para detectar y corrige a medida que una aplicación madura.


Actualización: Para demostrar que es perezoso ...

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace SingletonTest 
{ 
    class MySingleton 
    { 
     static public MySingleton Instance { get; } = new MySingleton(); 

     private MySingleton() { Console.WriteLine("Created MySingleton"); } 

     public void DoSomething() { Console.WriteLine("DoSomething"); } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Console.WriteLine("Starting Main"); 

      MySingleton.Instance.DoSomething(); 
     } 
    } 
} 

Salida:

Starting Main 
Created MySingleton 
DoSomething 
+0

Esto no es perezoso, sin embargo. – Blorgbeard

+0

Este * es * flojo. –

+0

Mi error: se olvidó cuando se ejecutaron los inicializadores estáticos. – Blorgbeard

Cuestiones relacionadas