Boldog.Mixins 2.1.0

dotnet add package Boldog.Mixins --version 2.1.0
                    
NuGet\Install-Package Boldog.Mixins -Version 2.1.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="Boldog.Mixins" Version="2.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Boldog.Mixins" Version="2.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Boldog.Mixins" />
                    
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 Boldog.Mixins --version 2.1.0
                    
#r "nuget: Boldog.Mixins, 2.1.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 Boldog.Mixins@2.1.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=Boldog.Mixins&version=2.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Boldog.Mixins&version=2.1.0
                    
Install as a Cake Tool

Boldog.Mixins

Write a C# source generator without writing a source generator.

Boldog.Mixins is a Roslyn incremental source generator that expands .mixin template files into real members on your partial types. Templates get a reflection-like, compile-time view of the type they're applied to — so the code they generate adapts to whatever it lands on.

.NET Standard 2.0 analyzer  ·  works in any C# 11+ project  ·  MIT


The problem

You hit the same C# pattern for the third time this month — value equality, a ToString, an interface delegated to a field, a mapper, a builder, a state machine. Your options:

  • Copy-paste it. Fast today; drifts tomorrow. Add a property and equality silently goes stale.
  • Reach for runtime reflection. Slow, allocates, fails at run time instead of compile time, and the trimmer hates it.
  • Write a Roslyn incremental source generator. The right tool — and a multi-day project. IIncrementalGenerator, syntax providers, value-equatable models so the cache doesn't thrash, a separate analyzer assembly, packaging, tests. All to stamp out members you could have described in a dozen lines.

The gap is real: the thing you want is "a generator," but the cost of authoring one is wildly out of proportion to the pattern you're trying to capture.

The solution

Write the pattern once, as a .mixin file. Boldog.Mixins turns it into an attribute. Apply the attribute to any partial type, and the template expands onto it as additional partial members — at build time, with no runtime cost.

The part that replaces a hand-written generator: inside the template you have mixin.Target, a reflection-like model of the decorated type — its properties, fields, methods, constructors, events, attributes, base type, interfaces, generic parameters, nested types — walkable to any depth, with generic type arguments substituted, all resolved by the compiler. Your template reads the shape of the type and generates code to fit it.

That's the whole pitch: the power of a custom source generator, at the cost of writing the code you wanted in the first place.

30-second example

Create a file named AutoToString.mixin:

public override string ToString()
{
    var parts = new System.Collections.Generic.List<string>();
    {{~ for $p in mixin.Target.Properties ~}}
    {{~   if $p.AccessSpecifier == "public" && !$p.IsStatic && $p.HasGetter ~}}
    parts.Add("{{ $p.Name }} = " + {{ $p.Name }});
    {{~   end ~}}
    {{~ end ~}}
    return "{{ mixin.Target.Name }} { " + string.Join(", ", parts) + " }";
}

The filename becomes an attribute. Apply it:

[AutoToString]
public partial class Order
{
    public int Id { get; set; }
    public string Customer { get; set; } = "";
    public decimal Total { get; set; }
}
new Order { Id = 7, Customer = "Acme", Total = 42m }.ToString()
// => "Order { Id = 7, Customer = Acme, Total = 42 }"

Add a property and rebuild — it shows up in ToString automatically, because the template reads the type's actual shape. No reflection, no copy-paste, no drift. You just wrote a source generator without writing a source generator.

Why you'll like it

  • No Roslyn boilerplate. No IIncrementalGenerator, no syntax providers, no equality models, no separate analyzer project. A template file and an attribute.
  • Zero runtime cost. Everything resolves at compile time into ordinary partial members. No reflection, no startup scan, trimmer- and AOT-friendly.
  • Type-aware, not text-blind. Templates branch on the target's real shape — kind, modifiers, members, nullability, attributes — and stay correct as the type evolves.
  • Library-safe by default. Generated attributes are internal, so mixins you define stay private to your assembly; the mixed-in members on your public types remain visible.
  • Edit-and-go. Change a .mixin file and generated code updates live — no rebuild ritual.
  • Honest about cost. When a template does something the incremental analyzer can't bound, it emits an info diagnostic (MIXIN0501) telling you exactly what, and how to avoid it. Output is always correct; only build incrementality is at stake.

Features

  • One template, every type kind. class, struct, record, record struct, interface — including nested types at any depth. The generator emits the matching partial wrapper.
  • Compile-time introspection (mixin.Target). A reflection-like graph: Properties, Fields, Methods, Constructors, Events, BaseType, Interfaces, NestedTypes, GenericParameters, Attributes — each with rich, value-accurate metadata.
  • Walk to any depth, with substitution. Descend into a property's type, its members' types, and so on. In-compilation types resolve full member graphs (enums and delegates included); Box<int> resolves Box<T>'s members with T mapped to int, through containers, arrays, and nullability.
  • Attribute-driven generation. Read [Attribute(...)] arguments off the target — including walkable typeof(...) references and enum values that round-trip back to compilable C# via the to_csharp_literal filter.
  • Parameters & generics. @parameter flows attribute arguments into the template; Name{T}.mixin produces a generic attribute whose type arguments become template variables.
  • Scriban templating. Full Scriban syntax, plus mixin-specific helpers (mixin.Target, to_csharp_literal).
  • Directives for the generated output: @namespace, @using, @parameter, @attribute, @inherits, @implements, @nullable.

Installation

Reference the package and mark it as an analyzer:

<PackageReference Include="Boldog.Mixins"
                  OutputItemType="Analyzer"
                  ReferenceOutputAssembly="false" />

That's it. The package's .props file automatically globs **/*.mixin under your project into AdditionalFiles — drop a .mixin file in and start using its attribute. (To scope the glob yourself, see the reference.)

Real-world examples

The repo ships 74 complete, build-verified samples in samples/Boldog.Mixins.Samples — every one compiles on each build and runs in the sample app. They re-create battle-tested mixin practice from across the language landscape (Ruby modules, Scala stackable traits, C++ CRTP, Rust derives, Lombok, Swift protocol synthesis, the GoF catalog, and the C# ecosystem's own Refit/Moq/StronglyTypedId). A few:

Implement an interface by delegating to a field — the textbook mixin. The template walks the interface's members and forwards every one, ref/out and generic methods included (sample 06):

[DelegateToFields]
public partial class AuditFacade(IAuditLog log) : IAuditLog
{
    readonly IAuditLog _log = log;   // every IAuditLog member is generated, forwarding to _log
}

Map an entity to a DTO by walking a typeof(...) argument and matching properties by name and type — extra properties on either side are simply skipped (sample 07):

[AutoMapper]
[MapSource(typeof(PersonEntity))]
public partial class PersonDto { /* Id, FullName, Email — PersonDto.From(entity) is generated */ }

Turn an attribute list into control flow. Repeated [Transition] attributes are the transition table; their enum arguments round-trip into a generated switch (sample 37):

[StateMachine]
[Transition(DocumentStatus.Draft,  DocumentStatus.Review)]
[Transition(DocumentStatus.Review, DocumentStatus.Published)]
public partial class DocumentWorkflow { public DocumentStatus Status { get; private set; } }

Generate a Refit-style HTTP client from an annotated interface — the most composite walk: typeof argument → interface methods → their route attributes → Task<T> unwrap (sample 50).

The full catalog with descriptions is in docs/samples.md.

Architecture & philosophy

  • Compile-time, not run-time. A mixin is ordinary generated C#. There is nothing to discover or dispatch at run time — it's as if you'd hand-written the members.
  • Correctness first, incrementality made visible. The generator statically bounds what each template's deep walk can read, so unrelated edits don't re-render targets. When a construct can't be bounded, it falls back conservatively and tells you (MIXIN0501) — output is always correct; the diagnostic just makes the build cost auditable and avoidable.
  • Symbol-free caching. The per-type pipeline embeds external type identity at materialization time rather than holding Roslyn symbols, keeping cached models small and value-equatable. (The one documented edge — a swapped reference under an otherwise-untouched session — is noted in docs/known-issues.md.)
  • Generic-attribute native. Generic mixins use C# 11 generic attributes; type arguments are first-class template variables.
  • No magic cascade. Mixins don't emit other mixins (Roslyn generators see only user source) — a deliberate, predictable boundary rather than a hidden one.

How it compares

Approach Authoring cost Runtime cost Adapts to the type Compile-time safety
Copy-paste / snippets Low (then drifts) None No Manual
Runtime reflection Low High (slow, allocates, AOT-hostile) Yes No (fails at run time)
T4 templates Medium None Limited (no semantic model) Weak
Hand-written Roslyn generator High (days) None Yes Yes
Boldog.Mixins Low (a template file) None Yes (mixin.Target) Yes

Boldog.Mixins occupies the slot the table makes obvious: the compile-time safety and type-awareness of a hand-written generator, at roughly the authoring cost of a snippet.

Documentation

  • Reference — the complete manual: directives, the full mixin.Target member model, file naming, generics, behavior notes, diagnostics, and limitations.
  • Template authoring guide — writing templates: graph-walking idioms, rendering values into C#, and the incremental-build (MIXIN0501) rules.
  • Introspection reference — the member-by-member object model behind mixin.Target, generated from the generator's own sources.
  • Samples — all 74 compiled samples, indexed.
  • Bundling mixins in a library — packaging .mixin files into a reusable NuGet library for downstream consumers.
  • Known issues — documented limitations and design record.

License

MIT.

Acknowledgements

Boldog.Mixins is built on Scriban by Alexandre Mutel (@xoofx) — the templating engine that powers .mixin files. Scriban is licensed under the BSD-2-Clause license; its copyright notice and license text are bundled with the package in THIRD-PARTY-NOTICES.txt.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has 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
2.1.0 110 6/23/2026
2.0.1 106 6/19/2026

- Bundles THIRD-PARTY-NOTICES.txt carrying Scriban's BSD-2-Clause copyright and license text (required when redistributing its binary), and credits Scriban in the README.
- Upgrades Scriban to 7.2.4 (verified against the template analyzer's AST soundness pin).
- Adds a documentation guide for bundling mixins in a reusable library.

Full changelog: https://gitlab.com/Boldog/Mixins/-/blob/main/CHANGELOG.md