12

I have the following piece of codes.

class Program
{
    static void Main(string[] args)
    {
        Enterprise.Initialize("Awesome Company");
        // Assertion failed when constructor of 'Reg' class is disabled.
        Debug.Assert(Reg.Root == @"Software\Awesome Company");
    }
}

public static class Enterprise
{
    // Static Properties.
    public static string Company
    {
        get;
        private set;
    }
    // Static Methods.
    public static void Initialize(string company)
    {
        Company = company;
    }
}
public class Reg
{
    public static string Root = $@"Software\{Enterprise.Company}";
    
    // ctor.
    static Reg()
    {
        // Assertion failed when this constructor is disabled.
    }
}

When executed, the assertion passed. However, the assertion failed when the constructor for Reg class is disabled. On a closer look, I've found that the implicit constructor of Reg class is called before Main(). If the constructor of Reg class is explicitly defined, it will be called after Main().

Why such discrepancy between implicit and explicit constructor?

Noctis Tong
  • 459
  • 1
  • 7
  • 17
  • 1
    I cannot reproduce this issue (VS2019, .Net Core 3.1) (I assume by "disable" you mean "comment out".) – Matthew Watson Sep 03 '20 at 07:45
  • what do you mean by "explicit" and "implicit" constructors? And how do you "disable" one? Those terms do not exist, as far as I know. – MakePeaceGreatAgain Sep 03 '20 at 07:45
  • For me, this assertion IS NOT failed when this constructor is disabled. – Roman Ryzhiy Sep 03 '20 at 07:46
  • @MatthewWatson I'm using vs 2019 .Net Framework 4.7.2. – Noctis Tong Sep 03 '20 at 07:48
  • @HimBromBeere implicit constructor is the constructor that generated by the compiler when I do not define the constructor. Explicit constructor is the constructor that I define in the class. – Noctis Tong Sep 03 '20 at 07:48
  • This .Net Fiddle works: https://dotnetfiddle.net/WoTkn9 – Matthew Watson Sep 03 '20 at 07:50
  • 1
    @HimBromBeere I put a break point to the static field in `Reg` and the very beginning of `Main()`. Execution break at the break point at the static field first when no constructor is defined. From the stack trace, it shows the execution comes from (implicit) constructor. – Noctis Tong Sep 03 '20 at 07:54
  • just ysterday I got a very similar problem. The suprising answer was: "debugging a static constructor is dicy". You never *really* know what´s happening. – MakePeaceGreatAgain Sep 03 '20 at 07:57
  • It is reproducable with .Net Framework: https://dotnetfiddle.net/yncMzC Looks like something was fixed in .Net Core – Matthew Watson Sep 03 '20 at 07:58
  • Note that there's no such thing as an "implicit (static) constructor". A type either has a static constructor or it doesn't... but that is part of what governs when the *type initializer* is executed. – Jon Skeet Sep 03 '20 at 08:03
  • @JonSkeet I got the "implicit (static) constructor" term from here https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors – Noctis Tong Sep 03 '20 at 08:08
  • 1
    Eek, that's not good. Will add some feedback to that page. – Jon Skeet Sep 03 '20 at 08:22
  • Filed https://github.com/dotnet/docs/issues/20432 - hopefully Bill Wagner will sort it out :) – Jon Skeet Sep 03 '20 at 08:25

1 Answers1

12

This is a quirk of chained static class initialization.

From the ECMA C# Specifications

15.5.6.2 Static field initialization

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 (§15.5.6.1). Within a partial class, the meaning of "textual order" is specified by §15.5.6.1. If a static constructor (§15.12) 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.

Take special note of the last part, this is your problem, if you don't have a static constructor you have no control over when the field is initialized. In your test case they are being initialized before you call Enterprise.Initialize

In short, you shouldn't be relying on these rules, it's easy to make mistakes and is likely to cause weird issues.

halfer
  • 19,824
  • 17
  • 99
  • 186
TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • 1
    Yes it indeed caused me some weird and funny issue. Will try to workaround it. – Noctis Tong Sep 03 '20 at 08:09
  • 1
    I ended up workaround the issue by defining static constructor in the static class to prevent compiler-generated static constructor from being called before `Main()`. – Noctis Tong Sep 03 '20 at 09:37
  • 1
    You could try [RuntimeHelpers.RunClassConstructor](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.runtimehelpers.runclassconstructor?view=netframework-4.7.2), which seems purpose made for addressing this. I've used it during startup to avoid long waits on the first method call after an API starts. – AjimOthy Sep 03 '20 at 17:27
  • @AjimOthy nice find, though i am not sure it will help in this situation. In truth chaining static classes should be avoided. – TheGeneral Sep 03 '20 at 22:56