C# tutorials > Modern C# Features > C# 6.0 and Later > What are module initializers in C# 9.0 and what are their use cases?
What are module initializers in C# 9.0 and what are their use cases?
Module Initializers in C# 9.0
C# 9.0 introduced module initializers, which are methods that the runtime automatically executes when a module (assembly) is loaded. They are declared using the This tutorial will explore module initializers, their use cases, and considerations for using them effectively.[ModuleInitializer]
attribute. Unlike static constructors, module initializers are guaranteed to run before any other code in the assembly, making them suitable for setting up conditions required by the rest of the code.
Basic Syntax
The ModuleInitializer
attribute, found in the System.Runtime.CompilerServices
namespace, is applied to a static method with no parameters and a void
return type. This method is then automatically executed by the .NET runtime before any other code in the assembly is run. Make sure to add the using System.Runtime.CompilerServices;
directive.
using System;
using System.Runtime.CompilerServices;
public static class ModuleInitializer
{
[ModuleInitializer]
public static void Initialize()
{
Console.WriteLine("Module Initializer executed!");
// Perform initialization logic here
}
}
Concepts Behind Module Initializers
Module initializers serve as a low-level mechanism to ensure that certain setup tasks are completed before any other code in an assembly executes. This is crucial in scenarios where the program's correctness depends on specific initial conditions. They are conceptually similar to static constructors but offer guaranteed early execution, which addresses potential issues with type initialization order and thread-safety in certain situations.
Real-Life Use Case: Registering Services in a Dependency Injection Container
A common use case is registering services within a dependency injection (DI) container. Module initializers can be used to configure the IServiceCollection
and build the ServiceProvider
before any application code attempts to resolve dependencies. This ensures that all necessary services are registered and available from the start. It's important to note that Microsoft.Extensions.DependencyInjection needs to be added as a NuGet package to use this code.
using System;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.DependencyInjection;
public static class ServiceRegistration
{
private static IServiceCollection _services;
[ModuleInitializer]
public static void RegisterServices()
{
_services = new ServiceCollection();
_services.AddSingleton<IMyService, MyServiceImpl>();
// Register other services here
ServiceProvider = _services.BuildServiceProvider();
}
public static IServiceProvider ServiceProvider { get; private set; }
public interface IMyService { }
public class MyServiceImpl : IMyService { }
}
Real-Life Use Case: Configuring Logging
Another use case is configuring logging frameworks. The module initializer configures the logger factory and builds an ILogger instance. Before the application's main execution begins, logging infrastructure is preconfigured. This ensures consistent logging from the very start of the application's lifecycle. Ensure to add the required NuGet packages like Microsoft.Extensions.Logging
and Microsoft.Extensions.Logging.Console
.
using System;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
public static class LoggingConfiguration
{
[ModuleInitializer]
public static void ConfigureLogging()
{
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
.AddConsole()
.AddEventSourceLogger();
});
ILogger = loggerFactory.CreateLogger<Program>();
}
public static ILogger ILogger { get; private set; }
private static void DoSomeLogging(ILogger logger)
{
logger.LogDebug("Starting up");
logger.LogInformation("Configuring services...");
logger.LogWarning("Example Warning!");
logger.LogError("Example Error!");
}
public static void Main(string[] args)
{
DoSomeLogging(ILogger);
}
}
Best Practices
Interview Tip
When discussing module initializers in an interview, highlight their use in performing crucial setup tasks before any application code runs. Emphasize their role in scenarios like dependency injection configuration or logging initialization. Also, mention the importance of keeping them simple and efficient to avoid delaying startup.
When to Use Them
Module initializers are ideal for scenarios where you need guaranteed early execution of initialization code before any other code in the assembly runs. They are particularly useful for setting up global state, registering services, or configuring logging infrastructure. They are also helpful when static constructors might not be the best option due to uncertainties in their execution order.
Memory Footprint
Module initializers themselves don't inherently have a significant memory footprint. However, the initialization logic they execute can impact memory usage. It's crucial to avoid creating large or unnecessary objects within module initializers, as this can increase the application's initial memory consumption. Optimize the initialization logic to minimize its memory footprint.
Alternatives
Alternatives to module initializers include:
Pros
Cons
FAQ
-
Can I have multiple module initializers in a single assembly?
Yes, you can have multiple module initializers in a single assembly. The runtime will execute them in an order that is deterministic but not necessarily defined by the source code. You should avoid creating dependencies between module initializers. -
What happens if a module initializer throws an exception?
If a module initializer throws an exception, the application might fail to start. It's essential to handle exceptions gracefully within module initializers to prevent unexpected application failures. -
Are module initializers thread-safe?
Module initializers are executed in a single thread during application startup. Therefore, you don't need to worry about thread-safety issues within module initializers unless you explicitly create and manage threads within them.