开发者

Creating an instance of a class in its static constructor - why is it allowed?

Another question on SO inspired me to try this code in C#:

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

    static void Main(string[] args) { }

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

This prints "Running" when run.

I actually expected t开发者_开发知识库he compiler to complain about this. After all, if the class has not yet been initialized by the static constructor; how can we be sure that it is valid to call methods on it ?

So why does the compiler not restrict us from doing this ? Is there any important usage scenarios for this ?

Edit

I am aware of the Singleton pattern; the point in question is why I can call a method on the instance before my static constructor finishes. So far JaredPar's answer has some good reasoning about this.


It is allowed because not allowing it would be a lot worse. Code like this would deadlock badly:

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;
    }
}

You are of course pointing a loaded gun at your foot.

This behavior is documented in the CLI Spec (Ecma 335) Partition II, Chapter 10.5.3.2 "Relaxed guarantees":

A type can be marked with the attribute beforefieldinit (§10.1.6) to indicate that the guarantees specified in §10.5.3.1 are not necessarily required. In particular, the final requirement above need not be provided: the type initializer need not be executed before a static method is called or referenced.

[Rationale: When code can be executed in multiple application domains it becomes particularly expensive to ensure this final guarantee. At the same time, examination of large bodies of managed code have shown that this final guarantee is rarely required, since type initializers are almost always simple methods for initializing static fields. Leaving it up to the CIL generator (and hence, possibly, to the programmer) to decide whether this guarantee is required therefore provides efficiency when it is desired at the cost of consistency guarantees.
end rationale]

The C# compiler indeed emits the beforefieldinit attribute on a class:

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


Slightly different question.

How would the compiler prevent you from doing this?

Sure it's very easy to detect in your sample, but what about this sample?

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

The ways in which you can trick the compiler to allow this are virtually endless. Even if the compiler got all of the answers you could still beat it with reflection.

In the end though this is actually legal, if a bit dangerous, code in both C# and IL. It is safe to do this as long as you are careful about accessing static's from within this code. It also is helpful / possibly necessary for certain patterns like Singleton's


Static constructor can initialize only static class members, this is not related to class instances and regular non-static class members.


What you may not realize is that for every class that does not have a non-static constructor, the compiler will generate one. This is different from your static constructor, which when you boil it down into MSIL is little more than a flag telling the CLR "Hey, run this code before you run what's in main()". So, the code of your static constructor is executed first. It instantiates a locally-scoped Program object using the NON-static constructor generated behind the scenes, and once instantiated, Run() is called on the object. Then, because you haven't stored this new object anywhere, it is disposed of when the constructor finishes executing. The main() function then runs (and does nothing).

Try this expansion:

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");
    }
}

See what the output is. My guess is that it will look something like this:

Instantiating a Program
Running
Finalizing a Program
main() called

The last two lines may be swapped because garbage collection may not get the chance to destroy the instance before main starts running (GC runs in a seperate managed thread, and so it works on its own time within the lifetime of the process), but the instance IS local to the static constructor in scope, and so is marked for collection before main() starts running. So, if you called Thread.Sleep(1000) in main() before printing the message, GC should collect the object in that time.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜