5

I have the following class:

public class AssignmentStatusCode 
{

    public static AssignmentStatusCode Pending { get; } = new AssignmentStatusCode("P");

    public static AssignmentStatusCode Rejected { get; } = new AssignmentStatusCode("R");

    public static AssignmentStatusCode Approved { get; } = new AssignmentStatusCode("A");


    public static implicit operator string(AssignmentStatusCode assignmentStatusCode)
    {
        return assignmentStatusCode.Value;
    }

    private static readonly HashSet<string> ValidStatusCodes = new HashSet<string>(new[] { "A", "R", "P" });

    public AssignmentStatusCode(string value)
    {
        if (!ValidStatusCodes.Contains(value))
        {
            throw new ArgumentOutOfRangeException(nameof(value),
                                                  $"Value must be {string.Join(", ", ValidStatusCodes.Select(c => $"'{c}'"))}.");
        }

        Value = value;
    }

    public string Value { get; }
}

When I create an instance of this class using var a = new AssignmentStatusCode("A"), a NullReferenceException is thrown on the if check of the instance constructor. Debugging indicates that ValidStatusCodes is null.

ValidStatusCodes has a static initializer on it.

According to the C# spec:

If a static constructor (§10.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.

Why isn't my static field getting initialized before it is being accessed in the constructor? I have a feeling that there is something very simple that I'm glossing over, but I've spent too much time debugging this with no progress; it's time to ask for help.


Apparently, if I had read the spec more closely, I would have noticed this at the beginning of the paragraph I quoted above, which was the root of my problem.

10.5.5.1 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. If a static constructor (§10.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

Thank you all for your help.

Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
  • 1
    Move `private static readonly HashSet ValidStatusCodes ...` before `public static AssignmentStatusCode Pending ...` and should works. – Alessandro D'Andria Feb 14 '19 at 14:40
  • For an explanation why Alessandro's answer will work see C# specification (ECMA-334) 17.11: "If a class contains any static fields with initializers, those initializers are executed in textual order immediately prior to executing the static constructor." – ckuri Feb 14 '19 at 14:44
  • Ahh, thank you. I wrote this class long ago, and forgot the that the static properties were also going through the constructor. When I added the static field this morning, I didn't notice it either. That's the simple thing I missed. – Bradley Uffner Feb 14 '19 at 14:46
  • 1
    I don't think you missed anything here. You only have one static *field*. The quoted documentation text does not tell us anything about static properties. – l33t Feb 14 '19 at 15:09

3 Answers3

4

Static Fields and properties are initialised in the order they appear in the class.

C# Spec

To Quote:

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.

Also for completeness:

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

Someone has pointed out in the comments and correctly so that this doesn't mention properties, just fields. I would say that auto properties are syntactic sugar over a private field and a property with get and set accessors, which is just more sugar so having Get() and Set(value) methods. So the above does apply to properties, the only caveat being that I dont know where and how the compiler orders those backing fields.

You initialise your Pending, Rejected and Accepted fields using the ctor that relies on a field that is after that fields being intialised.

Either put your hashset field first, so it gets initialised first. Or I think a better way would be to use a static ctor to initialise all this so you can clearly see the order and the dependencies of each become clearer. Also referring to the previous note about auto properties and where the compiler stores the private backing fields, it would be even more pertinent to use the static ctor and be fully confident of the order that they get the appropriate values set on them

Dave
  • 2,829
  • 3
  • 17
  • 44
  • Source? "Static Fields and properties are initialised in the order they appear in the class." – l33t Feb 14 '19 at 15:10
  • @l33t: [First result of Googling the thing you just quoted.](https://stackoverflow.com/questions/1494735/initialization-order-of-static-fields-in-static-class) – Flater Feb 14 '19 at 15:13
  • Try again! "Static Fields **and properties**." – l33t Feb 14 '19 at 15:14
  • 1
    I'm guessing that the initializer for a property is compiled to IL as an initializer for its backing field, which means the spec is technically correct. – Bradley Uffner Feb 14 '19 at 15:14
  • @l33t a fair point! I will find a good source and edit to include with an explanation – Dave Feb 14 '19 at 15:22
  • 1
    @BradleyUffner technically correct is the best kind of correct, but I'm going to edit and include some sources as that feels proper – Dave Feb 14 '19 at 15:23
  • 1
    For my guess about the initializer going to the backing field: From the [C# spec](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#automatically-implemented-properties) "An auto-property may optionally have a property_initializer, which is applied directly to the backing field as a variable_initializer (Variable initializers)." – Bradley Uffner Feb 14 '19 at 15:26
3

I think the problem is that on the line public static AssignmentStatusCode Pending { get; } = new AssignmentStatusCode("P"); your code contains an invocation of the instance constructor, which is executed before the HashSet's initialization.

I think the stacktrace of the exception you receives should show that the constructor's invocation thah raises error is the one we pointed to.

As suggested in a comment, you should move the private static readonly HashSet<string> ValidStatusCodes = new HashSet<string>(new[] { "A", "R", "P" }); before the line with the constructor call.

Pietro Martinelli
  • 1,776
  • 14
  • 16
0

your properties are initialized when instantiating your class according to the order in which you ordered them. ** ValidStatusCodes ** is suppose to be in the top of your class, the reason why you have this bug is that you call the constructor before initializing your ValidStatusCodes property.

sayah imad
  • 1,507
  • 3
  • 16
  • 24