Reputation: 16991
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.
Upvotes: 5
Views: 535
Reputation: 3017
Static Fields and properties are initialised in the order they appear in the class.
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
Upvotes: 4
Reputation: 1553
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.
Upvotes: 0
Reputation: 1906
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.
Upvotes: 3