3

I have a class with a static constructor:

private static readonly Dictionary<string, User> UsersByName;
private static readonly Dictionary<string, User> UsersByEmail;

static MyClass() {
     List<User> users = GetUsers();
     UsersByName = users.ToDictionary(user => user.Name);
     UsersByEmail = users.ToDictionary(user => user.Email);
}

private static List<User> GetUsers() { /* Make a WCF service call */ }

My problem was that whenever I made that WCF call, all of the worker threads in this web application died. After a lot of head scratching and binary chopping I found the root cause - it happened when we added UsersByEmail, and moved the code into a static constructor. Changing the code to the following fixed everything.

private static readonly List<User> AllUsers = GetUsers();
private static readonly Dictionary<string, User> UsersByName = AllUsers.ToDictionary(user => user.Name);
private static readonly Dictionary<string, User> UsersByEmail = AllUsers.ToDictionary(user => user.Email);

private static List<User> GetUsers() { /* Make a WCF service call */ }

Leaving aside the bad design of calling WCF from a static constructor (which break the app until it's restarted if the service happens to be down), because that is temporary code - I'm trying to find out why the change fix this problem. I ran it in the debugger many times, with the only difference being me adding or removing an empty static constructor; the call stack is always the same meaning the web service is called at the same time, so I don't see how being called from a class without a static constructor could make any difference.

configurator
  • 40,828
  • 14
  • 81
  • 115
  • Is it always the same event that triggers the type initialisation? (for example, do you always explicitly call `GetUsers` first, or are there any number of possible paths in your app that might trigger the initialisation?) – LukeH Jul 20 '11 at 09:39
  • And presumably you're already aware that `beforefieldinit` affects *what* can trigger initialisation, not just when: A `beforefieldinit` type will be initialised sometime before first access to any of its *static fields* (and if none of the static fields are touched then the type initialiser might never run, especially in .NET4). A non-`beforefieldinit` type will be initialised by pretty-much any member access, static or instance, at exactly the point when the member is accessed. – LukeH Jul 20 '11 at 10:00
  • @LukeH: In my tests I always used the same paths. What I can't understand is why something is different, even though the same thing triggered the initialization at exactly the same point. – configurator Jul 20 '11 at 14:25
  • How the initialisation is triggered might be relevent though. For example, if you call `GetUsers` explicitly at some point, your code with the static constructor will actually execute `GetUsers` twice (firstly in the type initialiser immediately followed by the explicit call). In .NET4, the code without a static constructor probably wouldn't execute the type initialiser at all if `GetUsers` was called explicitly. This is probably neither-here-nor-there, but maybe worth mentioning. – LukeH Jul 20 '11 at 14:42

1 Answers1

2

As I recall, beforefieldinit tells the JIT compiler that the static constructor can be run at any point before any static fields are referenced.

So, for example, if the class was first accessed inside a for loop, the JIT might try to instantiate it statically before the for loop begins, so it doesn't have to include code inside the for loop to check whether it's been instantiated during each iteration. Likewise, it's possible for a static method to be called without actually initializing any of the static properties, if the method doesn't touch any of those properties.

But if you include an explicit static constructor, it sets beforefieldinit to false, meaning the JIT-compiled code will wait until that point in the for loop where the class gets accessed before instantiating the class, and it will instantiate it at that point, regardless of whether the code actually touches any of the static fields.

So the short answer is, yes, it could have an impact on the order in which your code gets run, but it's pretty rare for it to cause problems, and I don't know why it would make a difference in this particular case.

See Jon Skeet's answer for more details.

Community
  • 1
  • 1
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315