2012-05-05 9 views
18

Mi pregunta surge después de refactorizar una clase que contenía solo métodos estáticos para declarar como una clase static, y experimentar problemas extraños al iniciar la aplicación.Riesgos potenciales con constructores estáticos en C#

No he realizado ninguna investigación exhaustiva, pero parece que alguna llamada realizada desde el constructor estático no se completa por alguna razón.

Entonces, me gustaría saber dónde hay algún inconveniente al usar constructores estáticos en C#? Más específicamente, ¿hay alguna cosa que se deba evitar a toda costa y que no se use desde dentro del constructor estático?

Respuesta

25

Hay varias dificultades para los constructores estáticos. Por ejemplo, si un constructor estático throws an exception, continuaría recibiendo un TypeInitializationException cada vez que acceda a cualquiera de sus miembros.

Si un constructor estático produce una excepción, el tiempo de ejecución no invocará una segunda vez, y el tipo se mantendrá sin inicializar durante la vida útil del dominio de aplicación en el que el programa se está ejecutando.

En general, las clases estáticas solo deben usarse en escenarios sin estado donde no necesitará ninguna inicialización. Si la clase tiene que ser inicializado, puede ser mejor usar el singleton pattern, que puede ser lazily initialized 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(); 
} 

Editar: Hice una cierta lectura más arriba en la materia, y parece que los constructores estáticos do causan interbloqueos si realiza operaciones de bloqueo (por ejemplo, devoluciones de llamada asincrónicas o sincronización de subprocesos) 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 hilo, inevitablemente se estancará. Como "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 Static constructor deadlocks artículo, proporcionando el siguiente ejemplo de un callejón sin salida:

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 hilo necesita tener acceso a la función anónima vacía, { }, define como su devolución de llamada. Sin embargo, dado que la función anónima se compila como otro método privado de MyClass entre bastidores, el nuevo subproceso no puede acceder antes de que se inicialice el tipo MyClass. Y, dado que el constructor estático MyClass necesita esperar a que el nuevo subproceso se complete primero (debido a thread.Join()), se produce un interbloqueo.

+0

¿Hay algo más que excepciones arrojadas dentro del constructor? Por ejemplo, ¿qué podría explicar el escenario de "punto muerto" que estoy experimentando? ¿Hay algún bloqueo relacionado con tipos estáticos de alguna manera detrás de escena? –

+0

@liortal: Respondido arriba. – Douglas

+0

¿Hay alguna diferencia entre lo que tienes en el primer ejemplo y estas líneas en su lugar? 'private static readonly Lazy current; static MyClass {current = new Lazy ((= = new new MyClass()); } ' (lo siento, parece que no puede obtener el formato correcto :)) – Mark

3

Sí, hay algunas dificultades, en su mayoría relacionadas con cuando la clase se inicializa. Básicamente, una clase con un constructor estático no se marcará con el indicador beforefieldinit, que permite que el tiempo de ejecución lo inicialice en otro momento.

Eche un vistazo a this article para más detalles.

0

Esta no es una respuesta a la pregunta, pero es demasiado larga para un comentario, así que la ofrezco aquí ...

Como yo no sé con static class constructo, he utilizado siguiente esquema (simplificado) para proveer de mí únicos:

public class SomeSingleton { 
    static _instance; 
    static public SomeSingleton Instance { 
     get { 
      if (_instance==null) { 
       _instance=new SomeSingleton(); 
      } 
      return _instance; 
     } 
    } 
} 

adelante, se utiliza

SomeSingleton.Instance.MyProp = 3; 

Y primer uso de el miembro Instance construirá su singleton.

Supongo que está bien, ya que la creación de instancias de los singletons se realiza en el orden correcto.

+0

No responde la pregunta ... y una clase estática es * no * lo mismo que un Singleton (por ejemplo, no puede pasar una clase estática como parámetro, lo que puede hacer con un singleton) –

+4

Su inicialización es no seguro para subprocesos Si se accede simultáneamente a la propiedad 'Instance' mediante varios hilos, es posible que obtengan instancias diferentes del" singleton ". Esto puede o no ser un problema en casos específicos, pero rompe el paradigma de singleton en general. Si está en .NET 4 (o posterior), debe cambiar a 'Lazy '; si no, debes considerar usar 'lock' para sincronizar la inicialización. – Douglas

+0

@Douglas gracias. Utilizo lock, ya que estoy en .net 2.0 :) –

Cuestiones relacionadas