Does every access to a static field enter a lock, check if the static
constructor is initalized, then initialize it if it isn't?
I doubt if it would be locking per-se, I guess the CLR just makes sure the IL is ordered an emitted in a way that its exclusive, though honestly i am not too sure.
Wouldn't this be very slow?
private static void Main(string[] args)
{
var t1 = Task.Run(
() =>
{
Console.WriteLine($"{DateTime.Now.TimeOfDay} here 1");
var val = Test.Value;
Console.WriteLine($"{DateTime.Now.TimeOfDay} here 1 complete");
return val;
});
var t2 = Task.Run(
() =>
{
Console.WriteLine($"{DateTime.Now.TimeOfDay} here 2");
var val = Test.Value;
Console.WriteLine($"{DateTime.Now.TimeOfDay} here 2 complete");
return val;
});
Task.WaitAll(t2, t2);
}
public static class Test
{
static Test()
{
Thread.Sleep(2000);
Value = 1;
}
public static int Value { get; }
}
Output
09:24:24.3817636 here 2
09:24:24.3817636 here 1
09:24:26.3866223 here 2 complete
09:24:26.3866223 here 1 complete
What you have here is not only extremely poorly written code, other threads have to wait around for these types of shenanigans to complete. So yes it can be slow if you choose it to be.
ECMA Specifications
15.12 Static constructors
The static constructor for a closed class executes at most once in a
given application domain. The execution of a static constructor is
triggered by the first of the following events to occur within an
application domain:
- An instance of the class is created.
- Any of the static members of the class are referenced.
...
Because the static constructor is executed exactly once for each
closed constructed class type, it is a convenient place to enforce
run-time checks on the type parameter that cannot be checked at
compiletime via constraints (§15.2.5).
There is no mentioned of how it accomplishes exclusivity (as you would expect) as its just an implementation detail, however what we do know is it does.
And lastly, because looking through the specifications is barrel of fun and hilarity (Individual results may vary), there are more weird situations you can your self in, like making circular dependencies
It is possible to construct circular dependencies that allow static
fields with variable initializers to be observed in their default
value state.
class A
{
public static int X;
static A()
{
X = B.Y + 1;
}
}
class B
{
public static int Y = A.X + 1;
static B() { }
static void Main()
{
Console.WriteLine("X = {0}, Y = {1}", A.X, B.Y);
}
}
produces the output
X = 1, Y = 2
To execute the Main method, the system first runs the initializer for
B.Y, prior to class B's static constructor. Y's initializer causes A's
static constructor to be run because the value of A.X is referenced.
The static constructor of A in turn proceeds to compute the value of
X, and in doing so fetches the default value of Y, which is zero. A.X
is thus initialized to 1. The process of running A's static field
initializers and static constructor then completes, returning to the
calculation of the initial value of Y, the result of which becomes 2.