3

I just noticed that the initialization of the static members which are returned via yield return is not in the order they are enumerated but in reverse order. Although the C1 instance is returned as first item of the iterator, the C2 instance is created first. If yielding more than 2 items, the last one is initialized first.

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);
    }
}
  • Is there a special reason why it is reversed?
  • Is this somewhere defined in the specification - or is it undefined and may change at some time in the future?
  • I would prefere if the items are constructed in the order I use them. Is there a way to ensure this?
LionAM
  • 1,271
  • 10
  • 28
  • 3
    [static initialization is not always deterministic](http://stackoverflow.com/questions/3681055) – D Stanley Nov 12 '15 at 16:11
  • Hence, if you make `Default` a property and initialize it lazily you should be good. – CompuChip Nov 12 '15 at 16:13
  • The title and first line of your question are very misleading - as you've acknowledged, the items *are* being yielded in the correct order; it's only initialization which is happening at unexpected times. – Jon Skeet Nov 12 '15 at 16:16

2 Answers2

5

As per the question comment, static initialization is not always deterministic. However, you can make it deterministic using static constructors:

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

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

That forces each of C1 and C2 to be initialized exactly at the point of first reference - neither earlier than that nor later than that. (Where "at the point of first reference" is defined to be construction of an instance or access to a static member.)

Without a static constructor, type initialization can be eager (i.e. occurs before you first use a type) or surprisingly late. You can create instances, call static and instance methods on a type and still not run the static field initializers - it's only when you refer to a static field that the static field initializers must have been run.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • That's really interesting - just adding the static constructor fixes it. And in my "real world" case, this is really simple because I just have to add it to a single generic base class (as I am using the "curiously recurring template pattern" and defined the Default field in the base class). However, I am still wondering why the order is reversed. If I instead return `new B[] { C1.Default, C2.Default }`, the initialization order is as expected. – LionAM Nov 12 '15 at 22:32
1

A declarative way of enforcing the order is to make your Default values come from a Lazy<T>:

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

Most C# programmers won't recognize that static C1() { } is changing the behavior of your program in a significant way. It's likely that the next person who goes to modify your code won't be Jon Skeet, so you're better off being explicit, or avoiding this business of relying on the order in the first place.

Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
  • Also a nice way to ensure the order - now I will have a hard time to decide which way to go... – LionAM Nov 12 '15 at 22:42