Executing code before Main in .NET

 
 
  • Gérald Barré

The Main method is the entry point of a C# application. When the application started, the Main method is the first method that is invoked.

-- Documentation

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.

Program.cs (C#)
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.

C#
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.

C#
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.

MyProject.csproj (csproj (MSBuild project file))
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net$(NETCoreAppMaximumVersion)</TargetFramework>
    <StartupHookSupport>false</StartupHookSupport>
  </PropertyGroup>
</Project>

You can also validate the value at runtime:

C#
// 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

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub