Reputation: 279
I wonder why this code doesn't end up in endless recursion. I guess it's connected to the automatic initialization of static members to default values, but can someone tell me "step by step" how does 'a' get the value of 2 and 'b' of 1?
public class A
{
public static int a = B.b + 1;
}
public class B
{
public static int b = A.a + 1;
}
static void Main(string[] args)
{
Console.WriteLine("A.a={0}, B.b={1}", A.a, B.b); //A.a=2, B.b=1
Console.Read();
}
Upvotes: 18
Views: 862
Reputation: 659994
Marc is correct. I would just add to his answer that your question is answered by section 10.5.5.1 of the specification, which states:
The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration. If a static constructor exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class.
Notice that last point. The spec goes on to quote your exact example as a case where either ordering is permitted by the specification; all the spec guarantees is that the field initializers are done in textual order before the static constructors run. It does not guarantee that fields of one type are initialized before or after fields of another type.
For example, the jit compiler is permitted to say "hey, I see that types A and B are used for the first time in this method that is about to be jitted, let me take a moment to make sure those types are loaded." The jitter is permitted to execute the field initializers at this time, and can choose to do A first or B first at its discretion.
In short: (1) you cannot rely on this behaviour; it is implementation-defined, and (2) the specification answers your exact question; consider reading the specification when you have a question about language semantics.
Upvotes: 12
Reputation: 65476
Since A.a is referenced first in the Console.WriteLine, its loaded first, which causes B to be loaded with the Value of A.a as 0 => B.b = 1 => A.a becomes 2
Reverse the print and watch it happen the other way.
Upvotes: 1
Reputation: 7981
Interestingly when I changed the order of output in your sample code around:
Console.WriteLine("B.b={0} A.a={1}", B.b, A.a);
I got the opposite results:
B.b=2 A.a=1
So it looks like it is to do with the order they are accessed
So, given that the output could change by adding an early usage of one of the variables it seems like such recursively defined values is A BAD IDEA(TM) :-)
Upvotes: 2
Reputation: 80734
The first type to load happens to be A
. So the type gets loaded, and it's static member a
gets it's default value of zero. After that, A
's static constructor get called. That constructor references type B
, so B
also gets loaded and it's static constructor gets called. That constructor, in turn, references type A
, but A
is already loaded, so nothing happens here, and b
gets it's value of zero (current value of a
) plus one, which is one. After that, static constructor of B
returns, and a
's value is calculated.
Upvotes: 2
Reputation: 1062600
I would suppose:
A.a
is queried, which causes the A
static initializer to fireB.b
, causing the B
static initializer to fireA.a
is queried; the type initializer is already activated (but no assignment has yet occurred), so the field (not yet assigned) is read as 0
0
+ 1
is 1
, which is assigned to B.b
<===========================B
cctor and go back to the A
cctor1
+ 1
is 2
, which is assigned to A.a
<===========================A
cctor2
is returned (WriteLine
) for A.a
WriteLine
) B.b
; the cctor has already fired so we see 1
Upvotes: 16
Reputation: 1099
It has to do with the order in which you access the static properties. The first evaluated is A.a. When evaluating A.a, B.b gets initialized. Since the actual assignment to a is not finished, the value of a remains 0, thus B.b becomes 1. After B.b is initialized, the value can be assigned to A.a, that is 1+1, thus 2
Upvotes: 6