C#
Source generator
Generate code at compile time with a source generator.
Source generator
In this lesson we add a DTO generator simulation. The static DtoGenerator class produces DTO class definitions from type names and adds them to a list with AddSource. This mimics the core flow of a real source generator.
The generator creates a class definition for each name with the hint name name + ".g.cs". Main calls the generator with three names and prints the hint names.
The DtoGenerator class
The static DtoGenerator class has three members: a List<(string Hint, string Source)> property, an AddSource(string, string) method, and a Generate(string[]) method. Generate adds each name with a .g.cs extension to the list.
This class shows how source generators produce code at compile time. The list preserves insertion order, so the output is deterministic.
Exercise Add the DtoGenerator class
Add a static class DtoGenerator to the project. Give it a public static System.Collections.Generic.List<(string Hint, string Source)> Sources { get; } = new(); property, a public static void AddSource(string hintName, string source) method (with Sources.Add((hintName, source));), and a public static void Generate(string[] typeNames) method. In Generate, loop foreach (var name in typeNames) and call AddSource(name + ".g.cs", $"public class {name}Dto {{ }}"). In Main, call DtoGenerator.Generate(new[] { "Info", "Warn", "Error" }) and print each entry.Hint with foreach (var entry in DtoGenerator.Sources).
Show example solution
DtoGenerator.Generate(new[] { "Info", "Warn", "Error" }); and foreach (var entry in DtoGenerator.Sources) Console.WriteLine(entry.Hint);
Example solution:
using System;
using System.Linq;
static class LogProcessor
{
public static int ParseLevel(string level)
{
return level switch
{
"INFO" => 0,
"WARN" => 1,
"ERROR" => 2,
_ => -1
};
}
public static int CountByLevel(string[] logs, int targetLevel)
{
int count = 0;
foreach (var log in logs)
{
if (ParseLevel(log.Split(':')[0]) == targetLevel)
{
count++;
}
}
return count;
}
public static string ParseWithSpan(ReadOnlySpan<char> log)
{
int colon = log.IndexOf(':');
if (colon < 0) return "";
return log.Slice(0, colon).ToString();
}
public static unsafe int UnsafeSum(int a, int b)
{
int* pa = &a;
int* pb = &b;
return *pa + *pb;
}
public static async System.Threading.Tasks.Task Run(string[] logs)
{
var channel = System.Threading.Channels.Channel.CreateUnbounded<string>();
var counts = new System.Collections.Concurrent.ConcurrentDictionary<string, int>();
foreach (var log in logs)
{
await channel.Writer.WriteAsync(log);
}
channel.Writer.Complete();
await foreach (var log in channel.Reader.ReadAllAsync())
{
var level = ParseWithSpan(log.AsSpan());
counts.AddOrUpdate(level, 1, (_, n) => n + 1);
}
foreach (var kvp in counts.OrderBy(k => k.Key))
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
}
}
static class DtoGenerator
{
public static System.Collections.Generic.List<(string Hint, string Source)> Sources { get; } = new();
public static void AddSource(string hintName, string source)
{
Sources.Add((hintName, source));
}
public static void Generate(string[] typeNames)
{
foreach (var name in typeNames)
{
AddSource(name + ".g.cs", $"public class {name}Dto {{ }}");
}
}
}
class Program
{
static void Main()
{
Console.WriteLine("LogProcessor skeleton ready");
var logs = new[] { "INFO: a", "WARN: b", "INFO: c", "ERROR: d" };
Console.WriteLine(LogProcessor.CountByLevel(logs, 0));
Console.WriteLine(LogProcessor.ParseWithSpan("ERROR: disk".AsSpan()));
Console.WriteLine(LogProcessor.UnsafeSum(3, 4));
LogProcessor.Run(logs).Wait();
DtoGenerator.Generate(new[] { "Info", "Warn", "Error" });
foreach (var entry in DtoGenerator.Sources)
{
Console.WriteLine(entry.Hint);
}
}
}
Test yourself
-
What is the purpose of a source generator?
- Do runtime reflection
- Read files
- Generate code at compile time
- Write tests
Show answer
Generate code at compile time — Correct. A source generator generates code at compile time.
-
Match each DtoGenerator step
- Generate
- Produce DTO classes
- AddSource
- Add generated code to the list
- .g.cs
- Generated file extension
-
Complete the AddSource call
AddSource(name + ____, source);
Show answer
AddSource(name + ".g.cs", source);
Next lesson: Putting it together