4
votes

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.

3
Move private static readonly HashSet<string> ValidStatusCodes ... before public static AssignmentStatusCode Pending ... and should works.Alessandro D'Andria
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
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
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

3 Answers

3
votes

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

2
votes

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.

0
votes

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.