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
}
Let's have class
B
, inheriting classA
. The rule of thumb is that when the static constructor of classB
is invoked, it first has make sure its ancestor, classA
, was initialized before. However, whenA
's static constructor is initialized first, having a dependency on its ancestorB
(which is weird anyway),B
's static constructor cannot be executed beforeA
's finishes, which results in anyB
'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:On the other hand, when you first access
A
anywhere in your code, then the sequence is as follows: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.