Patrón Singleton con C#
Uno de los patrones de diseño sin lugar a dudas más utilizado y conocido, es el patrón Singleton. De forma resumida, un singleton en una clase que únicamente permite que exista simultaneamente una única instancia de si misma y que ofrece un punto de acceso común a ella.
Este patrón nos puede ayudar en situaciones en las que queramos que haya únicamente una única instancia de una clase, por ejemplo para tener un acceso centralizado a un sistema de log o un sistema de caché, de forma que desde cualquier punto de la aplicación en el que queramos utilizar estos recursos, podamos garantizar que accedamos siempre a la misma instancia.
El código necesario para poder disponer de un singleton es realmente sencillo, son básicamente 2 líneas, que podremos ampliar con el comportamiento específico que queramos darle.
// .NET Singleton sealed class Singleton { private Singleton() {} public static readonly Singleton Instance = new Singleton(); } |
Para utilizar el código, podríamos usar desde cualquier punto Singleton.Instance
y accederíamos siempre a la misma instancia.
Entrando un poco más al detalle del código utilizado, merece la pena explicar un par de cosas referentes a cómo se crea la instancia y cómo se evitan posibles problemas que podrían provocarse, sobretodo en entornos multihilo.
Lo primero de todo es destacar como el constructor de la clase está marcado como «private», lo cual hace imposible crear instancias de la clase y nos obliga a acceder a la única posible instancia a través de la propiedad Instance. Con el mismo fin la clase está marcada como «sealed», por lo que nos garantizamos de que nadie va a poder heredar de esta clase y crear múltiples instancias de nuestro singleton.
En cuanto a la instancia de la clase, está marcada como «public», «readonly» y «static», lo cual hace que se pueda acceder a ella desde cualquier otra parte, que no sea modificable y que se cree la primera vez que se acceda a ella, como veremos más adelante.
Hay un par de cuestiones típicas que suelen afectar a otras implementaciones de este patrón en otros lenguajes, una es la inicialización perezosa y la segunda los posibles problemas con el uso de múltiples hilos. La inicialización perezosa se refiere a la conveniencia por eficiencia, de no crear la instancia hasta el momento en que se acceda por primera vez a la instancia. En otros lenguajes esto se realiza mediante un «if» en un método «getInstance», que comprueba si la instancia ha sido ya creada o no, de forma que se crea la primera vez que se llama al método. En situaciones multihilo esto puede provocar que varios hilos entre en el «if» a la vez, creando varias instancias. Para solventar este posible problema se suele utilizar una solución basada en un «Double-Check», en la cual, lo primero que hace el método «getInstance» es comprobar si la instancia ha sido creada y en caso contrario entra en una «sección crítica», en la cual se vuelve a realizar la comprobación y si sigue sin estar creada, se crea. De esta forma si dos hilos entran en el primer «if», al llegar a la sección crítica sus accesos se serializarían, produciendo que el primero de ellos crease la instancia y al entrar el segundo, la comprobación de si existe la instancia fuera positiva. A continuación puede verse el código correspondiente a este funcinamiento en C#, aunque no es necesario utilizarlo, ya que el código mostrado anteriormente tiene la misma funcionalidad como veremos a continuación.
// Port to C# class Singleton { public static Singleton Instance() { if (_instance == null) { lock (typeof(Singleton)) { if (_instance == null) { _instance = new Singleton(); } } } return _instance; } protected Singleton() {} private static volatile Singleton _instance = null; } |
Como hemos visto anteriormente, en el primer código de implementación del patrón, el atributo Instance está marcado como «static», con lo se consiguen los mismos resultados que con el código anterior, aprovechándonos de las características propias del Framework de .NET. En lo que respecta a la inicialización perezosa, el Framework de .NET crea las variables static la primera vez que se acceden a ellas, durante el proceso JIT (Just In Time compilation), de forma que si la variable no se usa, no se inicializa. Del mismo modo, en lo referente a la inicialización a salvo de problemas con múltiples hilos, el Framework garantiza que la inicialización de variables estáticas es «Thread safe».
Referencias
Hola,
Hay un caso en el que no tengo muy claro como actua el singleton. Lo explico y a ver que podéis decirme.
Tenemos una aplicación web alojada en IIS. Todas las aspx de dicha aplicación heredan de la página base en la cual tenemos una serie de instanciaciones de objetos, entre ellos web services.
Entra el primer usuario y activa la aplicación. Utiliza en una de las páginas uno de los servicios web invocados mediante singleton.
Más tarde entra otro usuario, llama ha dicho servicio.
¿Están utilizando los dos la misma instancia?
¿La instancia es únicamente para cada página o es para todas las páginas que heren de esa página base?
Creo que es un tema interesante,
Un saludo a todos!
Hola Mikel
Entiendo que los Singleton tienen algún método que por debajo utiliza una instancia de Web Service a la que se llama, y que esta instancia se crea al crear el singleton. Si esto es así, todas las peticiones que se hagan al singleton se harán a través de la misma instancia de proxy de Web Service.
En el caso de que la instanciación de estos web service no se realice en el Singleton, sino en la clase base de las páginas, en este caso sí que la instancia sería nueva para cada petición de página.
La diferencia radica en que el singleton está activo desde que se crea hasta que se cierra la aplicación (por un reinicio de IIS por ejemplo), sin embargo la instancia de página existe únicamente durante la petición de página, luego se destruye.
Espero que te sirva
Esto se puede considerar un singleton??
class DocumentInfo
{
private DocumentInfoTableAdapter _tableAdapter = null;
protected DocumentInfoTableAdapter Adapter
{
get
{
if (_tableAdapter == null)
{
_tableAdapter = new DocumentInfoTableAdapter();
}
return _tableAdapter;
}
}
}
no, puedes hacer tantos new DocumentInfo como quieras y además la comprobación que haces en el if debería estar dentro de una sección crítica, lee detenidamente el artículo.
Hola a tod@s.
Mi duda es la siguiente: Como en C# los atributos static sólo se crean cuando se usan, me encuentro con la pescadilla que se muerde la cola: si no uso mi instancia, no se crea, lo que implica no poder usarla… y si la uso, me dice que es null y da un error en tiempo de ejecucion… asi que no se como usar este código en mi aplicacion
sealed class Singleton
{
private Singleton() {}
public static readonly Singleton Instance = new Singleton();
}
Si pudieras colocar un ejemplo añadido de código funcional donde se empleara la clase singleton que describes arriba, te estaría muy agradecido
muchas gracias de antemano
un saludo a tod@s
atentamente: Jaime
Hola Jaime,
La instancia se crea cuando la usas por primera vez, eso no quiere decir que no puedas usarla, solo que no se crea hasta que se usa.
Si en la clase singleton por ejemplo incluimos un método doSomething(), podríamos llamarlo de la forma: Singleton.Instance.doSomething()
Al llamar a este método utilizamos el atributo Instance de Singleton, que provoca la creación de la instancia.
Espero haberte aclarado algo.
Un saludo
Hola!,
Sin duda un gran articulo. Pero me surge una duda que no sé si podrias aclararme.
Tengo una aplicacion en ASPNET con paginas aspx. Estas utilizan el ViewState para mantener los datos de la pagina. Si usamos un singleton para manejar dicho ViewState, que ocurre si entran varios usuarios la misma pagina en el mismo tiempo en la mismo servidor. Se podrian solapar la informacion que alberga el singleton??. es decir un usuario podria machacar la informacion que esta visualizando el otro usuario?
Hola Josua, un singleton es único para toda la aplicación por lo que no deberías gestionar el viewstate con uno.
Creo que debe declararse así:
public sealed class Singleton
de lo contrario no es accesible desde ningun lado.
saludos
Hola que tal, me gusto mucho como desarrollaste el tema…
Yo llegue a esta pagina con esta cuestión:
Quiero optimizar el uso de memoria RAM en una aplicación que es de escritorio con una base de datos distribuida, es decir, la misma aplicación en muchos sitios…
Yo uso DataSets tipados, y cada que cambio de form o que hago un movimiento se vuelve a cargar o se crean muchos datasets que ya había usado antes … solo que no tengo ni la mas remota idea de aplicarle singleton a este problema.
de antemano gracias y perdón por revivir un tema de hace 2 años