Source Code Generator

By Ayanami Kaine | Created on November 13, 2025 | Edited on November 13, 2025
C#

Source code generator are something I call poor men’s lisp macros. Source code generator like they are used in C# are fantastic if you don’t want or can use Reflection. In reality, they are much more complex than lisp macros.

A common use cases for source code generators are:

The thing is in every use case for source code generator you would use lisp macros.

Example: Boilerplate Reduction

C# Source Code Generator

// Source generator that creates ToString() methods
[Generator]
public class ToStringGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        var compilation = context.Compilation;
        var syntaxTrees = compilation.SyntaxTrees;
        
        foreach (var tree in syntaxTrees)
        {
            var root = tree.GetRoot();
            var classDeclarations = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
            
            foreach (var classDecl in classDeclarations)
            {
                var attributes = classDecl.AttributeLists;
                if (HasAttribute(attributes, "GenerateToString"))
                {
                    var generatedCode = GenerateToStringMethod(classDecl);
                    context.AddSource($"{classDecl.Identifier}_Generated.cs", generatedCode);
                }
            }
        }
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

// Usage
[GenerateToString]
public partial class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Generated code (compile-time)
public partial class Person
{
    public override string ToString()
    {
        return $"Person {{ Name = {Name}, Age = {Age} }}";
    }
}

Lisp Macro

; Define a macro that generates ToString at compile-time
(defmacro gen-to-string (class-name &rest fields)
  `(defmethod to-string ((obj ,class-name))
     (format nil "~a { ~{~a = ~a~^, ~} }"
             ',class-name
             (list ,@(mapcan (lambda (field)
                               `(',field (slot-value obj ',field)))
                             fields)))))

; Usage (macro expands at compile-time)
(gen-to-string person name age)

; Expands to:
(defmethod to-string ((obj person))
  (format nil "~a { ~{~a = ~a~^, ~} }"
          'person
          (list 'name (slot-value obj 'name)
                'age (slot-value obj 'age))))

TIP

Key Difference: The Lisp macro is just a function that transforms code. The C# source generator requires reflection, syntax tree parsing, attribute analysis, and file generation—all at compile-time but with much more ceremony.

Notice how the Lisp macro achieves the same result with far less boilerplate. The C# approach is more powerful for runtime scenarios but unnecessarily complex for cases where Lisp macros would suffice.