Strange behaviour with circular static readonly field definitions

Viewed 61

I got a bit of strange behaviour when running the following code in Visual Studio 2017 (Debug build):

using System;
using System.Collections.Generic;

namespace ConsoleApp2
{
    public class Program
    {
        public static class DefaultCustomers
        {
            public static readonly Customer NiceCustomer = new Customer() { Name = "Mr. Nice Guy " };
            public static readonly Customer EvilCustomer = new Customer() { Name = "Mr. Evil Guy " };
            public static readonly Customer BrokeCustomer = new Customer() { Name = "Mr. Broke Guy" };
        }

        public class Customer
        {
            public static readonly IEnumerable<Customer> UnwantedCustomers = new[] { DefaultCustomers.EvilCustomer, DefaultCustomers.BrokeCustomer };

            public string Name { get; set; }

            public override string ToString()
            {
                return Name;
            }
        }

        public static void Main(string[] args)
        {
            Console.WriteLine(new Customer() { Name = "Some other customer" });
            //Console.WriteLine(DefaultCustomers.NiceCustomer);

            foreach (var customer in Customer.UnwantedCustomers)
            {
                Console.WriteLine(customer != null ? customer.ToString() : "null");
            }

            Console.ReadLine();
        }
    }
}

The output on the console is

Some other customer
Mr. Evil Guy
Mr. Broke Guy

which is roughly the behaviour I expected. However, if I uncomment the second line in Program.Main(...), the output changes to

Some other customer
Mr. Nice Guy
null
null

I am aware that the problem can easily be fixed by turning UnwantedCustomers into a static readonly property.

But I wonder if the described behaviour is something that follows from the order in which the classes and objects are initialized, or if this behaviour is undefined?

1 Answers

You have an order of initialisation problem.

static fields (and properties) are initialised just before the static constructor runs (or would run if you had one). This is just before any reference to any members of the class are referenced (static or not).

With the lime commented out, when Customer.UnwantedCustomers is referenced it triggers the static construction of Customer which is preceded by the static construction of DefaultCustomers.

But with an easier reference to DefaultCustomers it triggers the static construction of DefaultCustomers which needs the static construction of Customer. This means the static properties of Customer are initialised before those of DefaultConstomers. And are thus null. In this case once the static construction of Customer has completed the static construction of DefaultCustomers will complete and thus DefaultCustomers.NiceCustomer has a value but Customer.UnwantedCustomers contains nulls.

This is well defined behaviour to cover cases like this with a behaviour that is predictable if not helpful.

Your problem is the circular reference between your two types. Putting UnwantedCustomers as a field of DefaultCustomers would avoid the problem.

Related