Executing code before Main in .NET
The
Main
method is the entry point of a C# application. When the application started, theMain
method is the first method that is invoked.
In fact, the Main
method may not be the first method of the assembly to be executed when the application starts. There are different methods that may run before the Main
method. In this post, I'll show you different ways to execute code before the Main
method. I don't say it is useful, only that it is possible 😃
#Static constructor
A static constructor is used to initialize any static data, or to perform a particular action that needs to be performed only once. It is called automatically before the first instance is created or any static members are referenced. So, the static constructor is called before the Main
method.
class Program
{
static Program() => Console.WriteLine("program.cctor");
static void Main() => Console.WriteLine("Hello, World!");
}
#Module initializers
Module initializers enable libraries to do eager, one-time initialization when loaded, and without the user needing to explicitly call anything. When the runtime loads the module (DLL), it calls the module initializer methods before executing any code from this module.
class Initializer
{
// The static ctor runs before the module initializer
static Initializer() => Console.WriteLine("Initializer.cctor");
[ModuleInitializer]
public static void Initialize1() => Console.WriteLine("Module Initializer 1");
[ModuleInitializer]
public static void Initialize2() => Console.WriteLine("Module Initializer 2");
}
class Program
{
static void Main() => Console.WriteLine("Hello, World!");
}
#Startup Hooks
The DOTNET_STARTUP_HOOKS
environment variable can be used to specify a list of managed assemblies that contain a StartupHook
type with a public static void Initialize()
method. Each of these methods will be called in the order specified, before the Main
entry point.
class StartupHook
{
static StartupHook() => Console.WriteLine("StartupHook.cctor");
// Start the application with the environment variable
// DOTNET_STARTUP_HOOKS=myapp.dll (use full path to the assembly)
public static void Initialize() => Console.WriteLine("Startup hook");
}
class Program
{
static void Main() => Console.WriteLine("Hello, World!");
}
Note that you can disable startup hooks by using the <StartupHookSupport>false</StartupHookSupport>
in the csproj. This will add the System.StartupHookProvider.IsSupported
runtime configuration switch.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net$(NETCoreAppMaximumVersion)</TargetFramework>
<StartupHookSupport>false</StartupHookSupport>
</PropertyGroup>
</Project>
You can also validate the value at runtime:
// method 1
// https://github.com/dotnet/runtime/blob/2f6f3c55e4c230ac9134abeaef494c17c01c97f7/src/libraries/System.Private.CoreLib/src/System/StartupHookProvider.cs#L21
var isSupported = Type.GetType("System.StartupHookProvider")
?.GetProperty("IsSupported", BindingFlags.Static | BindingFlags.NonPublic)
?.GetValue(null) ?? false;
// method 2
var isSupported = System.AppContext.TryGetSwitch("System.StartupHookProvider.IsSupported", out bool isHookSupported) && isHookSupported;
#Demo
#Additional resources
- Main() and command-line arguments
- Static Constructors
- Module Initializers
- ModuleInitializer attribute
- Host startup hook
Do you have a question or a suggestion about this post? Contact me!