Static Initializer/Constructor Ordering

122 views Asked by At

I ran into an interesting bug today, the code below would crash on the commented line on some machines, and not others. The problem appears to be related to ordering of static constructors, vs static initializers, and inheritance.

The fix was to move the code in the #region into another class, but I still don't understand what was actually happening, and why it seemed to only happen on some machines.

I have looked at these two articels:
http://csharpindepth.com/Articles/General/Singleton.aspx http://csharpindepth.com/Articles/General/BeforeFieldInit.aspx

which shed some insight, but neither goes into how inheritance effects things.

public class CountAggregator : Aggregator
{
    private static readonly CountAggregator _instance = new CountAggregator();

    public static CountAggregator Instance
    {
        get
        {
            return _instance; 
        }
    }

    private CountAggregator() : base("CNT")
    {
    }

}



public class Aggregator
{

    protected Aggregator(string id)
    {
        Id = id;
    }

    public string Id { get; private set; }





    #region All Aggregators

    private static readonly List<Aggregator> _allAggregators = new List<Aggregator>();
    private static readonly Dictionary<string, Aggregator> _aggregatorsById = new Dictionary<string, Aggregator>();

    public static IEnumerable<Aggregator> All
    {
        get { return _allAggregators; }
    }

    public static Aggregator GetAggregator(string id)
    {
        return _aggregatorsById[id];
    }

    static Aggregator()
    {
        _allAggregators.AddRange(new Aggregator[]
        {
             CountAggregator.Instance,
        }

        foreach (var aggregator in _allAggregators)
        {
            //this prints false, and the next line crashes
            HtmlPage.Window.Alert((aggregator != null).ToString());
            _aggregatorsById.Add(aggregator.Id, aggregator);                
        }        
    }

    #endregion 

}
1

There are 1 answers

1
Ondrej Tucny On

Let's have class B, inheriting class A. The rule of thumb is that when the static constructor of class B is invoked, it first has make sure its ancestor, class A, was initialized before. However, when A's static constructor is initialized first, having a dependency on its ancestor B (which is weird anyway), B's static constructor cannot be executed before A's finishes, which results in any B's field to be in their default (=zero, null) state.

When you first access B anywhere in your code, then the sequence is as follows:

Access B
    Invoke B's static constructor
        Invoke A's static constructor, if necessary
            initialize A's static fields
            execute the constructor's code
        initialize B's static fields
        execute the constructor's code

On the other hand, when you first access A anywhere in your code, then the sequence is as follows:

Access A
    Invoke A's static constructor
        initialize A's static fields
        execute the constructor's code, which includes
            Access B and its static field B.Field
                Invoke B's static constructor — circular dependency on A, cannot call
                return B.Field which is uninitialized i.e. zero / null
            use invalid value of B.Field

Your solution to pull out the code contained in the region is very logical and you should have done that anyway because of separation of concerns.