2010-09-02 15 views
6

Otra pregunta sobre lo que me inspiró para probar este código en C#:Crear una instancia de una clase en su constructor estático: ¿por qué está permitido?

class Program 
{ 
    static Program() 
    { 
     new Program().Run(); 
    } 

    static void Main(string[] args) { } 

    void Run() 
    { 
     System.Console.WriteLine("Running"); 
    } 
} 

Esto imprime "Running" cuando se ejecuta.

De hecho, esperaba que el compilador se quejara de esto. Después de todo, si la clase aún no ha sido inicializada por el constructor estático; ¿Cómo podemos estar seguros de que es válido llamar a los métodos?

Entonces, ¿por qué el compilador no nos impide hacer esto? ¿Hay algún escenario de uso importante para esto?

Editar

Conozco el patrón Singleton; El punto en cuestión es por qué puedo invocar un método en la instancia antes de que termine mi constructor estático. Hasta ahora, la respuesta de JaredPar tiene un buen razonamiento sobre esto.

+1

Con un patrón singleton, en realidad es * mejor práctica * crear instancias de la clase adjunta en el constructor estático (o en el inicializador de campo estático, que equivale a lo mismo) –

Respuesta

6

Está permitido porque no permitirlo sería un lote peor. Código como este sería un punto muerto:

class A { 
    public static readonly A a; 
    public static readonly B b; 
    static A() { 
     b = new B(); 
     a = B.a; 
    } 
} 

class B { 
    public static readonly A a; 
    public static readonly B b; 
    static B() { 
     a = new A(); 
     b = A.b; 
    } 
} 

Por supuesto, está apuntando con una pistola cargada en el pie.

Este comportamiento se documenta en la CLI Spec (ECMA 335) Partición II, capítulo 10.5.3.2 "garantías relajado":

Un tipo puede estar marcado con el atributo beforefieldinit (§10.1.6) para indicar que las garantías especificadas en §10.5.3.1 no son necesariamente requeridas. En particular, no es necesario proporcionar el requisito final anterior: el inicializador de tipo no necesita ejecutarse antes de que se llame o se haga referencia a un método estático.

[Justificación: Cuando el código se puede ejecutar en múltiples dominios de aplicación, se vuelve particularmente costoso garantizar esta garantía final. Al mismo tiempo, el examen de grandes cantidades de código administrado ha demostrado que rara vez se requiere esta garantía final, ya que los inicializadores de tipo son casi siempre métodos simples para inicializar campos estáticos.Dejarlo al generador CIL (y por lo tanto, posiblemente, al programador) para decidir si se requiere esta garantía, por lo tanto, proporciona eficiencia cuando se desea a costa de garantías de consistencia.
justificación final]

El compilador de C# de hecho emite la beforefieldinit atributo en una clase:

.class private auto ansi beforefieldinit ConsoleApplication2.Program 
     extends [mscorlib]System.Object 
{ 
    // etc... 
} 
0

El constructor estático solo puede inicializar los miembros de la clase estática, esto no está relacionado con las instancias de clase y los miembros regulares no estáticos de la clase.

6

Pregunta un tanto diferente.

¿Cómo evitaría el compilador que hiciera esto?

Seguro que es muy fácil de detectar en su muestra, pero ¿qué pasa con esta muestra?

class Program { 
    static void Fun() { 
    new Program(); 
    } 
    static Program() { 
    Fun(); 
    } 
} 

Las formas en que puede engañar al compilador para permitir esto son prácticamente infinitas. Incluso si el compilador obtuviera todas las respuestas, igual podría vencerlo con reflexión.

Al final, aunque esto es realmente legal, si es un poco peligroso, codifique tanto en C# como en IL. Es seguro hacerlo siempre que tenga cuidado con el acceso a estáticos desde este código. También es útil/posiblemente necesario para ciertos patrones como

de Singleton
+1

+1, un buen ejemplo. – driis

0

Lo que no se dan cuenta es que por cada clase que no tiene un constructor no estático , el compilador generará uno. Esto es diferente de su constructor estático, que cuando lo reduce a MSIL es poco más que una bandera que le dice al CLR "Oye, ejecuta este código antes de ejecutar lo que está en main()". Entonces, el código de su constructor estático se ejecuta primero. Crea una instancia de un objeto de programa de ámbito local utilizando el constructor NO estático generado detrás de las escenas, y una vez instanciado, se llama a Run() en el objeto. Entonces, como no ha almacenado este nuevo objeto en ninguna parte, se desecha cuando el constructor finaliza la ejecución. La función main() se ejecuta (y no hace nada).

Pruebe esta expansión:

class Program 
{ 
    static Program() 
    { 
     new Program().Run(); 
    } 

    public Program() 
    { 
     Console.WriteLine("Instantiating a Program"); 
    } 

    public override void Finalize() 
    { 
     Console.WriteLine("Finalizing a Program"); 
    } 

    static void Main(string[] args) { Console.WriteLine("main() called"); } 

    void Run() 
    { 
     System.Console.WriteLine("Running"); 
    } 
} 

ver lo que la salida es. Mi suposición es que va a ser algo como esto:

Instantiating a Program 
Running 
Finalizing a Program 
main() called 

Las dos últimas líneas se pueden intercambiar debido a la recolección de basura puede no tener la oportunidad de destruir la instancia antes principal se pone en marcha (GC se ejecuta en un subproceso administrado independiente, y entonces funciona en su propio tiempo dentro del tiempo de vida del proceso), pero la instancia ES local para el constructor estático en el alcance, y así se marca para la recopilación antes de que main() comience a ejecutarse. Entonces, si llamó a Thread.Sleep (1000) en main() antes de imprimir el mensaje, GC debería recoger el objeto en ese momento.

Cuestiones relacionadas