Use Case
Many business apps store reference codes in a database and reuse them across services, UI labels, and rules. Example:
| Code | Description |
|---|---|
| 1010001 | Processing |
| 1010002 | Completed |
| 1010003 | Cancelled |
The traditional pattern is to hard-code these values in C#:
public static class OperationCodes
{
public const string Processing = "1010001";
public const string Completed = "1010002";
public const string Cancelled = "1010003";
}
Problems: every DB change requires code edits and redeploys, mismatches can slip in, and the process is tedious.
Solution: Roslyn Source Generators
Roslyn Source Generators create C# code during compilation. Instead of hand-writing constants, the compiler emits them automatically from your source of truth (DB, JSON, etc.).
- Runs at compile-time (no runtime reflection or warm-up).
- Works in .NET 6/7/8+ with Visual Studio 2022 and the
dotnetCLI. - Generator project should target
netstandard2.0for analyzer compatibility.
Project Layout
CodeGenDemo.sln
├─ CodeGenDemo/ ← Console app (uses generated code)
│ └─ CodeGenDemo.csproj
└─ CodeGenDemo.Generators/ ← Roslyn source generator (analyzer)
└─ CodeGenDemo.Generators.csproj
1) Generator Project (CodeGenDemo.Generators)
CodeGenDemo.Generators.csproj — target netstandard2.0 and reference Roslyn packages:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
</ItemGroup>
</Project>
OperationCodeGenerator.cs — emits the OperationCodes class:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
namespace CodeGenDemo.Generators
{
[Generator]
public class OperationCodeGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context) { }
public void Execute(GeneratorExecutionContext context)
{
// In a real app, load from SQL/JSON/config.
var codes = new (string Code, string Name)[]
{
("1010001", "Processing"),
("1010002", "Completed"),
("1010003", "Cancelled")
};
var sb = new StringBuilder();
sb.AppendLine("namespace CodeGenDemo");
sb.AppendLine("{");
sb.AppendLine(" public static class OperationCodes");
sb.AppendLine(" {");
foreach (var c in codes)
{
var safeName = c.Name.Replace(" ", "");
sb.AppendLine($" public const string {safeName} = \"{c.Code}\";");
}
sb.AppendLine(" }");
sb.AppendLine("}");
context.AddSource("OperationCodes.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
}
}
}
Why netstandard2.0? Analyzers are loaded by the compiler’s host. Targeting net8.0 often causes load failures like: Could not load file or assembly 'System.Runtime, Version=8.0.0.0'. netstandard2.0 avoids this.
2) Console App Project (CodeGenDemo)
CodeGenDemo.csproj — reference the generator as an Analyzer (not a normal library):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\\CodeGenDemo.Generators\\CodeGenDemo.Generators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
</Project>
Important: Build the project before using OperationCodes. The source generator creates OperationCodes.g.cs at compile-time, so the class and its constants don’t exist until after a build. In microservices, you can generate the code in a shared service, package it as a NuGet, and let other services consume it safely.
Program.cs — use the generated constants:
using System;
namespace CodeGenDemo
{
class Program
{
static void Main()
{
Console.WriteLine(OperationCodes.Processing);
Console.WriteLine(OperationCodes.Completed);
Console.WriteLine(OperationCodes.Cancelled);
}
}
}
Build & Verify
- Build the solution (Visual Studio or
dotnet build). - The generator runs and emits
OperationCodes.g.csunder:CodeGenDemo/obj/Debug/net8.0/generated/CodeGenDemo.Generators/OperationCodes.g.cs - Run the app (F5 / Ctrl+F5 or
dotnet run --project CodeGenDemo).
Expected output:
1010001
1010002
1010003
Adapting to Your Data Source
Replace the hardcoded array in OperationCodeGenerator with a loader that reads from your chosen source of truth:
- JSON/CSV file in the solution (easy, deterministic).
- SQL via a pre-build step that exports a JSON file for the generator to read (generators should avoid runtime DB connections).
The generator consumes that file at compile time and regenerates constants automatically.
Benefits
- No manual syncing between DB and code.
- Compile-time safety with strongly typed constants.
- Zero runtime overhead (all code is generated at build time).
- Works across environments (CI, CLI, VS) with no extensions.