2008-10-08 16 views
15

Mientras se trabaja en una aplicación de C# Acabo de notar que en varios lugares inicializadores estáticos tienen dependencias entre sí de esta manera:Orden de constructores/inicializadores estáticos en C#

static private List<int> a = new List<int>() { 0 }; 
static private List<int> b = new List<int>() { a[0] }; 

Sin hacer nada especial que trabajó. ¿Eso es solo suerte? ¿Tiene C# reglas para resolver esto?

Editar: (re: Panos) En un archivo, el orden léxico parece ser el rey? ¿Qué pasa con los archivos?

Al observar que intentaron una dependencia cíclica como esto:

static private List<int> a = new List<int>() { b[0] }; 
static private List<int> b = new List<int>() { a[0] }; 

y el programa no corrieron la misma (el traje prueba fallida a través del tablero y no miré más).

+0

Creo que a través de los archivos (es decir, en diferentes clases) sucederá lo mismo. Durante la inicialización de tipo de la clase A, se le solicitará a la clase B que se inicialice, y la clase B encontrará una referencia nula a la clase A. – Panos

+0

Ahora, en los archivos de la misma clase (clase parcial), probablemente corresponda al preprocesador determinar si falla o no. – Panos

+0

Entonces, si A hace referencia a B.b, entonces A.a entrará en contacto con B.b? – BCS

Respuesta

13

Se parece depender de la secuencia de líneas. Este código funciona:

static private List<int> a = new List<int>() { 1 }; 
static private List<int> b = new List<int>() { a[0] }; 

mientras que este código no funciona (que arroja un NullReferenceException)

static private List<int> a = new List<int>() { b[0] }; 
static private List<int> b = new List<int>() { 1 }; 

Así que, obviamente, no existen reglas para la dependencia cíclica. Es curioso sin embargo, que el compilador no se queja ...


EDITAR - ¿Qué está pasando "a través de los archivos"? Si declaramos estas dos clases:

public class A { 
    public static List<int> a = new List<int>() { B.b[0] }; 
} 
public class B { 
    public static List<int> b = new List<int>() { A.a[0] }; 
} 

y tratar de acceder a ellos con este código:

try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message.); } 
try { Console.WriteLine(A.a); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); } 
try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); } 

estamos recibiendo esta salida:

The type initializer for 'A' threw an exception. 
Object reference not set to an instance of an object. 
The type initializer for 'A' threw an exception. 

Así que la inicialización de B causa una excepción en el constructor estático A y campo de izquierdas a con el valor predeterminado (nulo). Como a es null, b tampoco se puede inicializar correctamente.

Si no tenemos dependencias cíclicas, todo funciona bien.


EDIT: En caso de que no leyó los comentarios, Jon Skeet proporciona una lectura muy interesante: The differences between static constructors and type initializers.

+0

Interesante, por lo que la inicialización no ocurre hasta que se hace referencia a las cosas ... – BCS

+0

Esto es seguro. Se llama al constructor estático cuando se hace referencia al tipo por primera vez. – Panos

+2

Tenga cuidado aquí acerca de la diferencia entre los inicializadores de variables estáticas y los constructores estáticos. Existen diferentes reglas en torno a cuándo se produce la inicialización del tipo en función de la presencia/ausencia de un constructor estático. Consulte http://pobox.com/~skeet/csharp/beforefieldinit.html –

0

Sí, tenías suerte. C# parece ejecutar el código en el orden en que aparece en la clase.

static private List<int> a = new List<int>() { 0 }; 
static private List<int> b = new List<int>() { a[0] }; 

funciona, pero ...

static private List<int> b = new List<int>() { a[0] }; 
static private List<int> a = new List<int>() { 0 }; 

fallará.

Yo recomendaría poner todas sus dependencias en un solo lugar, el constructor estático es el lugar para ello.

static MyClass() 
{ 
    a = new List<int>() { 0 }; 
    b = new List<int>() { a[0] }; 
} 
2

Personalmente me gustaría deshacerse de los inicializadores estáticos, ya que no está claro y añadir un constructor estático para inicializar estas variables.

static private List<int> a; 
static private List<int> b; 

static SomeClass() 
{ 
    a = new List<int>() { 0 }; 
    b = new List<int>() { a[0] }; 
} 

Entonces no tiene que adivinar qué está pasando y está siendo claro en sus intenciones.

+2

Tenga en cuenta que esos fragmentos de código no son * exactamente * equivalentes en términos de cuándo se ejecutan: http://pobox.com/~skeet/csharp /beforefieldinit.html –

14

Ver section 10.4 of the C# spec de las reglas aquí:

cuando se inicializa una clase, todos los campos estáticos de esa clase se inicializan primero en sus valores por defecto, y luego los inicializadores de campo estático se ejecutan en orden textual. Del mismo modo, cuando se crea una instancia de una clase, todos los campos de instancia en esa instancia se inicializan primero a sus valores predeterminados, y luego los inicializadores del campo de instancia se ejecutan en orden textual. Es posible que se observen campos estáticos con inicializadores variables en su estado de valor predeterminado. Sin embargo, esto se desaconseja fuertemente como una cuestión de estilo.

Así, en otras palabras, en su ejemplo 'b' se inicializa a su estado por defecto (null) y por lo que la referencia a ella en el inicializador de la 'a' es legal, pero daría lugar a una NullReferenceException.

Estas reglas son diferentes a las de Java (ver section 8.3.2.3 of the JLS para ver las reglas de Java sobre referencias directas, que son más restrictivas).

+0

* Gran respuesta * a la pregunta que hice. Sin embargo, parece que no hice la pregunta que quería: ¿qué pasa con los archivos? – BCS

+0

No soy un genio de C# (solo sabía dónde buscar) pero echa un vistazo a http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx - "Es posible construir dependencias circulares que permiten que los campos estáticos con inicializadores variables se observen en su estado de valor predeterminado ". ¿Eso ayuda? – Cowan

+0

@Cowan no realmente. No aborda el problema del archivo cruzado. – BCS