Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Static Initializer With (and Without) Mixed Static Constructors

I've been through the relevant section of C# Language Spec (v5.0) but I can't find the piece that's relevant to what I'm seeing.

If you have a run of the code below, you'll see the output below, which is what I expect:

using System;

class Test {
   static int count = 0;
   static void Main() {
      Console.WriteLine("In Main(), A.X=" + A.X);
   }

   public static int F(string message) {
      Console.WriteLine(message);
      A.X = ++count;

      Console.WriteLine("\tA.X has been set to " + A.X);
      B.Y = ++count;

      Console.WriteLine("\tB.Y has been set to " + B.Y);
      return 999;
   }
}
class A {
   static A() { }
   public static int U = Test.F("Init A.U");
   public static int X = Test.F("Init A.X");
}

class B {
   static B() { }
   public static int R = Test.F("Init B.R");
   public static int Y = Test.F("Init B.Y");
}

The output is:

Init A.U
    A.X has been set to 1
Init B.R
    A.X has been set to 3
    B.Y has been set to 4
Init B.Y
    A.X has been set to 5
    B.Y has been set to 6
    B.Y has been set to 2
Init A.X
    A.X has been set to 7
    B.Y has been set to 8
In Main(), A.X=999

This is exactly the output I'd expect. In particular, notice that even though method F() is executing with the parameter, "Init A.U", it is called again (interrupted, if you like) once the reference to B.Y is encountered, causing B's static initializers to execute. Once B's static constructor completes, we return again to the A.U invocation of F(), which accounts for B.Y being set to 6 and then to 2. So, hopefully this output makes sense to everyone.

Here's what I'm not understanding: If you comment out B's static constructor, this is the output you see:

Init B.R
        A.X has been set to 1
        B.Y has been set to 2
Init B.Y
        A.X has been set to 3
        B.Y has been set to 4
Init A.U
        A.X has been set to 5
        B.Y has been set to 6
Init A.X
        A.X has been set to 7
        B.Y has been set to 8
In Main(), A.X=999

Sections 10.5.5.1 and 10.12 of the C# Spec (v5.0) indicate A's static constructor (and its static initializers) are triggered to execute when "any of the static members of the class are referenced." Yet here we have A.X referenced from within F() and A's static constructor is not triggered (since its static initializers are not running).

Since A has a static constructor I would expect those initializers to run (and interrupt) the "Init B.R" call to F(), just as B's static constructor interrupted A's call to F() in the "Init A.U" call that I showed at the beginning.

Can anyone explain? Af face value it looks like a violation of the spec, unless there's some other part of the spec that allows this.

Thanks

like image 288
Tom Baxter Avatar asked Apr 17 '15 23:04

Tom Baxter


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr. Stroustroupe.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

How old is the letter C?

The letter c was applied by French orthographists in the 12th century to represent the sound ts in English, and this sound developed into the simpler sibilant s.


1 Answers

I think I see what's going on here, although I don't have a good explanation for why it is this way.

The test program is a little too coarse to see what's happening. Let's make a minor adjustment:

class Test {
   static int count = 0;
   static void Main() {
      Console.WriteLine("In Main(), A.X=" + A.X);
   }

   public static int F(string message) {
       Console.WriteLine("Before " + message);
       return FInternal(message);
   }

   private static int FInternal(string message) {
      Console.WriteLine("Inside " + message);
      A.X = ++count;

      Console.WriteLine("\tA.X has been set to " + A.X);
      B.Y = ++count;

      Console.WriteLine("\tB.Y has been set to " + B.Y);
      return 999;
   }
}
class A {
   static A() { }
   public static int U = Test.F("Init A.U");
   public static int X = Test.F("Init A.X");
}

class B {
   static B() { }
   public static int R = Test.F("Init B.R");
   public static int Y = Test.F("Init B.Y");
}

The output is similar to that in the question, but with more detail:

Before Init A.U  
Inside Init A.U  
    A.X has been set to 1  
Before Init B.R  
Inside Init B.R  
    A.X has been set to 3  
    B.Y has been set to 4  
Before Init B.Y  
Inside Init B.Y  
    A.X has been set to 5  
    B.Y has been set to 6  
    B.Y has been set to 2  
Before Init A.X  
Inside Init A.X  
    A.X has been set to 7  
    B.Y has been set to 8  
In Main(), A.X=999

Nothing surprising here. Remove B's static constructor and this is what you get:

Before Init A.U  
Before Init B.R  
Inside Init B.R  
    A.X has been set to 1  
    B.Y has been set to 2  
Before Init B.Y  
Inside Init B.Y  
    A.X has been set to 3  
    B.Y has been set to 4  
Inside Init A.U  
    A.X has been set to 5  
    B.Y has been set to 6  
Before Init A.X  
Inside Init A.X  
    A.X has been set to 7  
    B.Y has been set to 8  
In Main(), A.X=999

Now this is interesting. We can see that the original output was misleading. We actually start by trying to initialize A.U. That is not surprising because A should be initialized first because A.X is accessed in Main. The next part is interesting. It looks like when B does not have a static constructor the CLR interrupts the method that is going to access B's fields (FInternal) before it enters the method. Contrast this with the other case. There the initialization of B was delayed until we actually accessed the fields of B.

I'm not entirely sure why things are done in this particular order, but you can see that the reason the initialization of B is not interrupted to initialize A is that the initialization of A has already started.

like image 162
Mike Zboray Avatar answered Oct 01 '22 22:10

Mike Zboray