Yak 1.2.0

dotnet add package Yak --version 1.2.0
                    
NuGet\Install-Package Yak -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Yak" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Yak" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Yak" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Yak --version 1.2.0
                    
#r "nuget: Yak, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Yak@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Yak&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Yak&version=1.2.0
                    
Install as a Cake Tool

Yak

Yak is simple inversion of control for humans. It leverages Source Generators in Roslyn to create compile time IoC containers with simple, declarative interface.

The name comes from Yet Another Inversion of Control (YAIC resembles word YAK).

Quick start

Let's say you have a couple of classes in your project:

public interface ISingletonComponent { }

public class SingletonComponent : ISingletonComponent { }

public class TransientComponent
{
    public ISingletonComponent SingletonComponent { get; }

    public TransientComponent(ISingletonComponent singletonComponent)
    {
        SingletonComponent = singletonComponent;
    }
}

To make your own IoC container, create a partial class annotated with [Module]. Mark properties with lifetime attributes ([Singleton] or [Transient]) and use partial properties — Yak automatically generates getters that resolve dependencies via constructors at compile time.

using Yak;

[Module]
public partial class MyModule
{
    [Singleton<SingletonComponent>]
    public partial ISingletonComponent SingletonComponent { get; }

    [Transient<TransientComponent>]
    public partial TransientComponent TransientComponent { get; }
}

Usage is straightforward:

using MyModule myModule = new MyModule();

ISingletonComponent singleton1 = myModule.SingletonComponent;
ISingletonComponent singleton2 = myModule.SingletonComponent;

// True — singletons always return the same instance
Console.WriteLine("Singleton is identical: {0}", ReferenceEquals(singleton1, singleton2));

TransientComponent transient1 = myModule.TransientComponent;
TransientComponent transient2 = myModule.TransientComponent;

// False — transients return a new instance each time
Console.WriteLine("Transient is identical: {0}", ReferenceEquals(transient1, transient2));

// True — transient instances share the same singleton dependency
Console.WriteLine("Singleton dependencies are identical: {0}",
    ReferenceEquals(transient1.SingletonComponent, transient2.SingletonComponent));

Module imports

Modules can depend on other modules via constructor parameters. Properties from imported modules are available for dependency resolution:

[Module]
public partial class InfraModule
{
    [Singleton<DatabaseService>]
    public partial IDatabaseService Database { get; }
}

[Module]
public partial class AppModule(InfraModule infra)
{
    [Transient<UserRepository>]
    public partial UserRepository UserRepository { get; }
    // UserRepository(IDatabaseService) is resolved from infra.Database
}
using InfraModule infra = new InfraModule();
using AppModule app = new AppModule(infra);

UserRepository repo = app.UserRepository;

Module interfaces

Interfaces annotated with [Module] define reusable, composable sets of services. A concrete [Module] class that implements such interfaces gets generated property implementations for all lifetime-attributed properties in the interface hierarchy. Properties without lifetime attributes are requirements the concrete class must provide.

[Module]
public interface IInfrastructure
{
    Config Config { get; }  // no attribute — consumer must provide

    [Singleton]
    Database Database { get; }

    [Singleton]
    Logger Logger { get; }
}

[Module]
public interface IMessaging : IInfrastructure
{
    [Singleton]
    MessageBus MessageBus { get; }
}

[Module]
public interface IAnalytics : IInfrastructure
{
    [Singleton]
    Tracker Tracker { get; }
}

Compose interfaces via standard C# interface inheritance. Diamond inheritance resolves naturally — shared services are generated once:

[Module]
public interface IFullStack : IMessaging, IAnalytics { }

[Module]
partial class AppModule : IFullStack
{
    public Config Config { get; } = new Config { Environment = "production" };

    [Singleton]
    public partial OrderService OrderService { get; }
}
using AppModule app = new AppModule();

app.OrderService.PlaceOrder("widget");

// Database is created once and shared — diamond inheritance just works.
Console.WriteLine(ReferenceEquals(app.Database, app.OrderService.Tracker.Database));  // True

If the concrete class declares a property that matches an interface property, the class declaration wins — this lets you override specific registrations.

Lifetimes

  • Singleton — one instance per module, created on first access
  • Transient — new instance on every access

Use the generic form ([Singleton<T>], [Transient<T>]) to specify a concrete implementation type when the property type is an interface.

Callbacks

Methods annotated with [OnActivate] or [OnDispose] are called when dependencies are created or when the module is disposed. Callbacks are matched by parameter type:

[Module]
public partial class MyModule
{
    [Singleton<SingletonComponent>]
    public partial ISingletonComponent SingletonComponent { get; }

    [OnActivate]
    protected void OnComponentCreated(ISingletonComponent component)
    {
        // Called when SingletonComponent is first created
    }

    [OnDispose]
    protected void OnComponentDisposed(ISingletonComponent component)
    {
        // Called when the module is disposed
    }
}

Callbacks propagate across module imports. If a provider module defines callbacks, they also fire for matching services created by importing modules:

[Module]
public partial class InfraModule
{
    public List<IUpdatable> Updatables { get; } = new();

    [OnActivate]
    void Register(IUpdatable u) => Updatables.Add(u);

    [OnDispose]
    void Deregister(IUpdatable u) => Updatables.Remove(u);
}

[Module]
public partial class AppModule(InfraModule infra)
{
    [Singleton<PlayerSystem>]
    public partial IUpdatable PlayerSystem { get; }
    // PlayerSystem is automatically registered/deregistered via InfraModule's callbacks
}

Factories

Mark a property with [Factory<T>] or [StaticFactory<T>] to opt out of constructor injection. Yak locates a matching method on T, calls it, and wraps the result with lifetime management, callbacks, and dispose tracking. The attribute name declares the dispatch:

  • [Factory<T>] — calls a public instance method on T. T must be in the module graph (a registered property, an imported module, or the current module class itself).
  • [StaticFactory<T>] — calls a public static method on T. T can be any named type.

Use [Factory<CurrentModule>] (or [StaticFactory<CurrentModule>]) when the factory method lives on the module itself. Private helpers are allowed on self-reference:

[Module]
public partial class MyModule
{
    [Singleton]
    public partial Config Config { get; }

    [Singleton]
    [Factory<MyModule>]
    public partial Connection Connection { get; }

    private Connection Build(Config config) => Connection.Open(config);
}

Use [Factory<TSource>] when the factory is an instance method on another registered service — Yak resolves the source through the graph and calls the method on that instance:

[Module]
public partial class MyModule
{
    [Singleton]
    public partial ConnectionFactory ConnectionFactory { get; }

    [Singleton]
    [Factory<ConnectionFactory>]
    public partial Connection Connection { get; }
    // emits: ConnectionFactory.Open(...)
}

Use [StaticFactory<TSource>] when the factory is a public static method on any type — including the service's own type, which is the idiomatic way to wire up types whose only construction path is a static Create/Open:

public class Connection : IDisposable
{
    private Connection(Config config) { /* ... */ }
    public static Connection Open(Config config) => new Connection(config);
    public void Dispose() { /* ... */ }
}

[Module]
public partial class MyModule
{
    [Singleton]
    public partial Config Config { get; }

    [Singleton]
    [StaticFactory<Connection>]
    public partial Connection Connection { get; }
    // emits: Connection.Open(Config)
}

public static class Helpers
{
    public static Connection Make(Config config) => Connection.Open(config);
}

[Module]
public partial class OtherModule
{
    [Singleton]
    public partial Config Config { get; }

    [Singleton]
    [StaticFactory<Helpers>]
    public partial Connection Connection { get; }
}

Factory method parameters are resolved from the module graph exactly like constructor parameters. Candidate methods must not be generic, async, or extension methods, and must have a return type matching the property's declared (or [Singleton<TImpl>]/[Transient<TImpl>]) type. If multiple candidates match, pass an explicit method name: [Factory<MyModule>("Build")] or [StaticFactory<Helpers>(nameof(Helpers.Make))].

Diagnostics

ID Severity Description
YAK001 Error A module type is imported more than once across constructors
YAK002 Error A property has a constructor dependency that cannot be resolved
YAK003 Error A lifetime property is missing the partial modifier
YAK007 Error A factory property has multiple matching candidates
YAK008 Error A factory property has no matching candidate
YAK009 Error A factory type argument is not a named class, interface, or struct
YAK010 Error A [Factory<T>] source type is not in the module graph (use [StaticFactory<T>] for public statics)

How it works

Yak is a source generator that detects classes annotated with [Module]. For each module, it generates a partial class with property getter implementations that handle lifetime management and constructor-based dependency resolution. When a module class implements [Module] interfaces, the generator also produces implementations for interface-declared properties. Everything is handled at compile time without any run-time reflection.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 162 4/12/2026
1.1.1 117 4/12/2026
1.1.0 115 4/12/2026
1.0.0 118 4/11/2026
0.0.5 607 8/29/2022
0.0.4 530 8/23/2022
0.0.3 515 8/23/2022
0.0.2 525 8/23/2022
0.0.1 568 7/18/2022