Skip to content

Source Generation

Mika Notarnicola edited this page Jul 18, 2026 · 5 revisions

Source Generation

Bootstrap ships a small Roslyn source generator that cuts down on boilerplate for two specific things: singleton-style access to a service, and per-class loggers. Both are opt-in attributes you apply to your own classes.

Requirements

A class using either attribute must be declared partial. The generator adds a second declaration of the same type in a companion file, and C# requires every part of a partial type to agree on the partial modifier (and on accessibility, so in practice the class should be public).

To use either attribute outside of Bootstrap's own code, the asmdef your class lives in needs a reference to BeardPhantom.Bootstrap.SourceGenAttributes.dll.

[GenerateSingleton]

Generates a static accessor for a service, so callers don't have to go through Services's App.Locate<T>() directly. Underneath, the generated member is just a thin wrapper around ServiceRef<T>.Instance.

[GenerateSingleton]
public partial class MatchmakingService : IService
{
    // ...
}

generates:

public partial class MatchmakingService
{
    public static MatchmakingService Instance => ServiceRef<MatchmakingService>.Instance;
}

The attribute takes an optional SingletonAccessors flag to control which member(s) get generated:

Value Generates
SingletonAccessors.Property (default) A public static {Type} Instance property
SingletonAccessors.OutMethod A public static void GetInstance(out {Type} instance) method. This pattern can be used to discourage multiple calls to a .Instance property.

Both can be combined, e.g. [GenerateSingleton(SingletonAccessors.Property | SingletonAccessors.OutMethod)], to generate both members on the same class.

[GenerateLogger]

Generates a static, per-class ILogger field:

[GenerateLogger]
public partial class MatchmakingService : IService
{
    // ...
}

generates:

public partial class MatchmakingService
{
    private static readonly Microsoft.Extensions.Logging.ILogger s_logger = LogUtility.GetStaticLogger(nameof(MatchmakingService));
}

The category is always the class's own name; the attribute takes no parameters and there's no way to override it. Once generated, s_logger is a standard ILogger, so it supports the usual ZLogger extension methods (ZLogTrace, ZLogDebug, ZLogInformation, ZLogWarning, ZLogError, ZLogCritical):

s_logger.ZLogInformation($"Match {matchId} started.");

[GenerateLogger] isn't limited to services. It's a general-purpose attribute, though it's commonly applied to them, as in Bootstrap's own DefaultLogService.

This attribute only does anything meaningful in an assembly that also references Bootstrap's ZLogger integration, since the generated field depends on LogUtility from that package.

Applying Both

The two attributes are independent and commonly stacked on the same class:

[GenerateSingleton]
[GenerateLogger]
public partial class MatchmakingService : IService
{
    public void InitService(BootstrapContext context)
    {
        s_logger.ZLogInformation($"{nameof(MatchmakingService)} initialized.");
    }
}

Clone this wiki locally