A “module initializer” is a function that is run when an assembly is first loaded. In many ways this is like a static constructor in C#, but rather than applying to one class it applies to the entire assembly.
This feature has existed in the CLR since the beginning, but until now it was not exposed by C#. Under the Module Initializers proposal, this would be exposed as a modification to the static constructor syntax.
[module: ModuleInitializer(typeof(MyModuleInitializer))]
internal static class MyModuleInitializer
{
static MyModuleInitializer()
{
// put your module initializer here
}
}
As you can see from this example, a module-level attribute is tagged with a class name. That class’s static constructor is then promoted to the module initializer level.
This feature could result in performance gains over normal static constructors. Mark Smeltzer writes,
Currently the runtime takes an init lock which is used to double check whether the static construction logic has been processed. Adding even one static read-only field to a class instantly adds that overhead to every external use of any member of that class.
Having the ability to run initialization logic in a guaranteed, predictable order that has zero post module init runtime performance would be a huge benefit.
Another benefit is a module initializer is predictable; all of the code in it runs sequentially. With static constructors, the order in which they run is non-deterministic from the assembly’s standpoint. Depending on the client code, the constructor for Class A may run before or after Class B.
Terminology notes:
A “module” in the .NET CLR sense is a file containing IL code. An “assembly” is logical unit consisting of one or more modules, one of which is designated as the head assembly. Most .NET languages are designed to only create single-module/single-file assemblies. So, for most developers, the terms are interchangeable.
A “module” in the VB sense is what C# calls a “static class”.
A “satellite assembly” resembles a multi-module/ multi-file assembly in some ways, but it is a separate concept.
Community comments
Clarification...
by Mark Smeltzer,
Re: Clarification...
by Jonathan Allen,
Fody ModuleInit
by Igr Alexánder Fernández Saúco,
Clarification...
by Mark Smeltzer,
Your message is awaiting moderation. Thank you for participating in the discussion.
Module initializers still have the aforementioned benefits, but when I made my original comment I was unaware of some of the recent improvements to tiered compilation available in .NET Core 3.0...
Tiered-compilation in .NET Core 3.0+ solves the readonly static member access penalty. See github.com/dotnet/coreclr/issues/24571#issuecom... for more information. That feature shipped in the 3.0 release of .NET Core.
What it does is pretty awesome: the runtime initially produces quick to compile but non optimal JIT code as needed and then executes it. Then in the background the runtime analyzes the context and IL code to determine if a more optimal JIT solution can be achieved. If so, it recompiles the IL code and swaps out the slow JIT path with the optimal JIT path. I'm sure there's some additional complexity in how that's achieved, but that's the basic idea.
For static class initializers and static readonly field initializers, the runtime will produce first pass code that takes the init lock. That protects against the initializers ever running twice. That locking also has a runtime performance penalty. So, once the initializer runs once, the optimizer will simply produce new optimized accessor code paths that don't do any locking anymore!
Again, I'm not clear on the specifics of how that is achieved (I'm sure it was quite complicated to keep track of the dependent accessors that need to be optimized post-init), but the net benefit is pretty huge: the runtime itself automagically optimizes away the performance penalty of using static initializers and static readonly fields.
Re: Clarification...
by Jonathan Allen,
Your message is awaiting moderation. Thank you for participating in the discussion.
Thank you for the update. That is very interesting.
Fody ModuleInit
by Igr Alexánder Fernández Saúco,
Your message is awaiting moderation. Thank you for participating in the discussion.
It is possible use module initializers via Fody ModuleInit Weaver
github.com/Fody/ModuleInit/blob/master/readme.md