Metaprogramming, Mixins & Experimental Interceptors
Use C# 14 extension members for mixin-like reusable behavior, understand where metaprogramming fits, and examine interceptors accurately as an experimental compiler feature rather than a standard C# 14 feature.
Part 1: What You Will Learn
C# does not have a dedicated mixin keyword. Instead, reusable behavior can be composed with interfaces, delegation, extension members, source generators, and other compile-time techniques.
- Use C# 14 extension members to add behavior and properties to an abstraction.
- Understand mixin-like composition without inheritance.
- Distinguish runtime reflection from compile-time metaprogramming.
- Understand what interceptors do and why they remain experimental.
Project setup: Create a .NET 10 Console App named MetaprogrammingDemo and use C# 14.
Part 2: Runnable C# 14 Mixin-Style Example
The following code uses a C# 14 extension block. Any type implementing IAuditable gains the AddAudit method and LatestAudit extension property without inheriting from a common base class.
public interface IAuditable
{
List<string> AuditEntries { get; }
}
public sealed class Customer : IAuditable
{
public required string Name { get; init; }
public List<string> AuditEntries { get; } = [];
}
public static class AuditExtensions
{
extension(IAuditable target)
{
public void AddAudit(string message)
{
target.AuditEntries.Add(
$"{DateTimeOffset.Now:u} {message}");
}
public string LatestAudit =>
target.AuditEntries.Count == 0
? "No audit entries"
: target.AuditEntries[^1];
}
}
var customer = new Customer { Name = "Alicia" };
customer.AddAudit("Customer created");
customer.AddAudit("Email address verified");
Console.WriteLine(customer.Name);
Console.WriteLine(customer.LatestAudit);Part 3: Where Interceptors Fit
Important: interceptors are still an experimental compiler feature. They are not listed among the standard C# 14 language features and should not be presented as ordinary production C#.
An interceptor can substitute a specific method call at compile time. In practice it is designed primarily for tooling and source-generator scenarios because the interceptor must identify a precise interceptable source location.
Current compiler guidance requires explicit opt-in. A project experimenting with interceptors includes a setting such as:
<PropertyGroup> <TargetFramework>net10.0</TargetFramework> <LangVersion>14.0</LangVersion> <Features>InterceptorsPreview</Features> </PropertyGroup>
The interceptor itself must match the target call's signature and carry compiler-generated location metadata. Because that metadata is tied to an exact source position and the feature may change, manually hard-coding an interceptor into a beginner example is fragile.
- Extension members: stable C# 14 feature; suitable for reusable APIs and mixin-like behavior.
- Source generators: compile-time code generation; useful for removing repetitive boilerplate.
- Interceptors: experimental compile-time call substitution; use only when deliberately exploring compiler/tooling scenarios.
Part 4: Mini Project & Practice
Mini project: create interfaces named IHasValidation and IHasAuditTrail. Give each interface reusable C# 14 extension members, then apply both behaviors to an Order class without creating a deep inheritance hierarchy.
As an advanced exercise, create a separate experimental project for interceptors rather than mixing preview compiler features into production code.
Key idea: metaprogramming is powerful when it removes boilerplate or enables tooling, but it should make a codebase easier to maintain—not merely more clever.
When you are comfortable with this lesson, continue to Lesson 40.
C# in Visual Studio 2026
📘 This lesson is part of the book C# in Visual Studio 2026 by Dr. Liew Voon Kiong.
View on Amazon Kindle Edition