que - Posibles trampas con constructores estáticos en C#
eliminar instancia de una clase c# (3)
Mi pregunta surge después de refactorizar una clase que contenía solo métodos estáticos para ser declarados como una clase static
y experimentar problemas extraños al iniciar la aplicación.
No he realizado ninguna investigación exhaustiva pero parece que alguna llamada que se realiza desde dentro del constructor estático no se completa por algún motivo.
Entonces, me gustaría saber dónde hay algún inconveniente al usar constructores estáticos en C #. Más específicamente, ¿hay cosas que se deben evitar a toda costa y no se pueden usar desde el constructor estático?
Esto no es una respuesta a la pregunta, pero es demasiado largo para un comentario, así que lo ofrezco aquí ...
Como no sabía de construcción de static class
, he usado el siguiente esquema (simplificado) para proporcionarme singletons:
public class SomeSingleton {
static _instance;
static public SomeSingleton Instance {
get {
if (_instance==null) {
_instance=new SomeSingleton();
}
return _instance;
}
}
}
Luego usas
SomeSingleton.Instance.MyProp = 3;
Y el primer uso del miembro Instance
construirá su singleton.
Supongo que está bien ya que la creación de instancias de los singletons si hay muchas de estas clases se realiza en el orden correcto.
Hay varios escollos para los constructores estáticos. Por ejemplo, si un constructor estático lanza una excepción , continuará obteniendo una TypeInitializationException
cada vez que acceda a cualquiera de sus miembros.
Si un constructor estático lanza una excepción, el tiempo de ejecución no lo invocará una segunda vez, y el tipo permanecerá sin inicializar durante la vida útil del dominio de aplicación en el que se ejecuta su programa.
En general, las clases estáticas solo deben usarse en escenarios sin estado donde no necesitará ninguna inicialización. Si necesita inicializar su clase, puede que sea mejor que utilice el patrón de singleton , que se puede iniciar con pereza en el primer acceso:
public class MyClass
{
private static readonly Lazy<MyClass> current =
new Lazy<MyClass>(() => new MyClass());
public static MyClass Current
{
get { return current.Value; }
}
private MyClass()
{
// Initialization goes here.
}
public void Foo()
{
// ...
}
public void Bar()
{
// ...
}
}
static void Main(string[] args)
{
MyClass.Current.Foo(); // Initialization only performed here.
MyClass.Current.Bar();
MyClass.Current.Foo();
}
Edición : Hice una lectura adicional sobre el tema, y parece que los constructores estáticos causan puntos muertos si realiza operaciones de bloqueo (por ejemplo, devoluciones de llamada asíncronas o sincronización de hilos) dentro de ellos.
El CLR utiliza internamente el bloqueo para evitar que los inicializadores de tipo (constructores estáticos) se ejecuten varias veces al mismo tiempo. Por lo tanto, si su constructor estático intenta acceder a otro miembro de su tipo declarante desde otro subproceso, inevitablemente se interrumpirá. Dado que "otro miembro" podría ser una función anónima declarada como parte de una operación PLINQ o TPL, estos errores pueden ser sutiles y difíciles de identificar.
Igor Ostrovsky (MSFT) explica esto en su artículo de Interbloqueo de constructores estáticos , que proporciona el siguiente ejemplo de un interbloqueo:
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();
}
}
En el ejemplo anterior, el nuevo subproceso necesita acceder a la función anónima vacía, { }
, definida como su devolución de llamada. Sin embargo, dado que la función anónima se compila como otro método privado de MyClass
detrás de escena, el nuevo hilo no puede acceder a ella antes de que se inicialice el tipo MyClass
. Y, dado que el constructor estático MyClass
debe esperar a que el nuevo subproceso se complete primero (debido a thread.Join()
), se produce un interbloqueo.
Sí, hay algunos escollos, principalmente relacionados con el momento en que se inicializa la clase. Básicamente, una clase con un constructor estático no se marcará con el indicador beforefieldinit
, que permite que el tiempo de ejecución se beforefieldinit
en un momento posterior.
Echa un vistazo a este artículo para más detalles.