yield return 语句中静态成员的初始化顺序

Order of initialization of static members in yield return statements

我刚刚注意到通过 yield return 编辑的静态成员的初始化不是按照它们被枚举的顺序而是相反的顺序。虽然 C1 实例被 returned 作为迭代器的第一项,但首先创建 C2 实例。如果生成超过 2 个项目,则首先初始化最后一个。

public abstract class B
{
    private static int _number;

    protected B()
    {
        Number = ++_number;
    }
    public int Number { get; private set; }
}

class C1 : B
{
    public static readonly C1 Default = new C1();
}

class C2 : B
{
    public static readonly C2 Default = new C2();
}

public static IEnumerable<B> GetItems()
{
    yield return C1.Default;
    yield return C2.Default;
}

private static void Main(string[] args)
{
    var items = GetItems();

    foreach (var item in items)
    {
        Console.WriteLine(item.Number);
    }
}

根据问题评论,static initialization is not always deterministic。但是,您可以使用静态构造函数使其具有确定性:

class C1 : B
{
    static C1(){}
    public static readonly C1 Default = new C1();
}

class C2 : B
{
    static C2(){}
    public static readonly C2 Default = new C2();
}

这迫使 C1C2 中的每一个都在第一次引用时准确地初始化 - 既不早于也不迟于。 (其中 "at the point of first reference" 定义为构造实例或访问静态成员。)

如果没有静态构造函数,类型初始化可能会很急(即发生在您首次使用类型之前)或出奇地晚。您可以创建实例,在类型上调用静态和实例方法,但仍然不能 运行 静态字段初始值设定项 - 只有当您引用静态字段时,静态字段初始值设定项 必须 已经 运行.

强制执行命令的一种声明方式是使您的 Default 值来自 Lazy<T>:

class C1 : B
{
    private static readonly Lazy<C1> _default = new Lazy<C1>(() => new C1());
    public static C1 Default { get { return _default.Value; } }
}

大多数 C# 程序员不会认识到 static C1() { } 正在显着改变程序的行为。下一个去修改你的代码的人很可能不是 Jon Skeet,所以你最好直截了当,或者首先避免这种依赖订单的业务。