静态字段初始化程序未在实例构造函数之前运行

时间:2019-02-14 14:37:42

标签: c#

我有以下课程:

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

当我使用var a = new AssignmentStatusCode("A")创建此类的实例时,实例构造函数的NullReferenceException检查中将抛出if。调试表明ValidStatusCodesnull

ValidStatusCodes上有一个静态初始化程序。

根据C#规范:

  

如果类中存在静态构造函数(第10.12节),   静态字段初始化程序的执行紧接在   执行该静态构造函数。 否则,静态字段   初始化程序在与实现相关的时间执行,然后   该类的静态字段的首次使用。

为什么在构造函数中访问我的静态字段之前未对其进行初始化?我感觉有些事情很简单,但是我花了很多时间调试它,但是没有任何进展。是时候寻求帮助了。


显然,如果我更仔细地阅读了规范,我会在上面引用的段落的开头注意到这一点,这就是问题的根源。

  

10.5.5.1静态字段初始化类的静态字段变量初始化器对应于以下分配序列:   按照在班级中出现的文字顺序执行   声明。如果类中存在静态构造函数(第10.12节),   静态字段初始化程序的执行紧接在   执行该静态构造函数。否则,静态字段   初始化程序在与实现相关的时间执行,然后   该类的静态字段的首次使用

谢谢大家的帮助。

3 个答案:

答案 0 :(得分:3)

静态字段和属性按照它们在类中出现的顺序进行初始化。

C# Spec

引用:

  

类的静态字段变量初始值设定项对应于按其在类声明中出现的文本顺序执行的一系列分配。

为了完整性:

  

如果类中存在静态构造函数,则在执行该静态构造函数之前立即执行静态字段初始化程序。否则,将在首次使用该类的静态字段之前,在与实现相关的时间执行静态字段初始化程序。

有人正确地指出了注释,因此这里没有提及属性,只是字段。我要说的是,自动属性是一个私有字段上的语法糖,是具有get和set访问器的属性,这只是更多糖,因此具有Get()和Set(value)方法。因此,上述内容确实适用于属性,唯一的警告是我不知道编译器在哪里以及如何排序这些后备字段。

您可以使用初始化字段之后依赖于字段的ctor来初始化“待处理”,“已拒绝”和“接受”字段。

要么将您的哈希集字段放在第一位,要么首先对其进行初始化。或者我认为更好的方法是使用静态ctor初始化所有这些,以便您可以清楚地看到每个对象的顺序和依赖性。还请参考前面有关自动属性的说明,以及编译器在其中存储私有后备字段的地方,使用静态ctor并更加确信它们获得在其上设置的适当值的顺序​​将更加有意义

答案 1 :(得分:2)

我认为问题在于,public static AssignmentStatusCode Pending { get; } = new AssignmentStatusCode("P");行中的代码包含实例构造函数的调用,该实例构造函数在HashSet初始化之前执行。

我认为您收到的异常的堆栈跟踪应该表明构造函数的调用引发了错误,这是我们所指出的。

如注释中所建议,您应将private static readonly HashSet<string> ValidStatusCodes = new HashSet<string>(new[] { "A", "R", "P" });移动到构造函数调用的行之前。

答案 2 :(得分:0)

根据实例化类的顺序实例化类时,将初始化您的属性。 ** ValidStatusCodes **假定位于类的顶部,之所以出现此错误,是因为您在初始化ValidStatusCodes属性之前先调用了构造函数。