Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C# & .NET (C# 14, .NET 10 LTS)  Module 18 – Reflection, attributes and source generators

Writing an incremental source generator

Build a C# incremental source generator step by step: the project, the pipeline, models that cache, diagnostics, tests with the driver, and a NuGet package.

  • Advanced
  • 15 minutes
  • Examples run with .NET 10.0.401 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 5 of 15 minutes free
Free: A generator is a netstandard2.0 library that the compiler loads · Initialize sets up a pipeline that turns an attribute into a file. With a pass: 3 in-depth parts and 3 interview answers, about 10 minutes.

What you will learn

  • Build an IIncrementalGenerator that finds an attribute with ForAttributeWithMetadataName and writes a file per type
  • Set up a generator project for netstandard2.0 and reference it from a project that runs it
  • Design value-equatable models that keep a generator incremental, and prove the caching with tracked steps
  • Test a generator with CSharpGeneratorDriver, and report problems as diagnostics instead of throwing
  • Package a generator under analyzers/dotnet/cs, and choose the Roslyn version it references

Before you start

What you’ll learn · Before you start

What you will learn

  • Build an IIncrementalGenerator that finds an attribute with ForAttributeWithMetadataName and writes a file per type
  • Set up a generator project for netstandard2.0 and reference it from a project that runs it
  • Design value-equatable models that keep a generator incremental, and prove the caching with tracked steps
  • Test a generator with CSharpGeneratorDriver, and report problems as diagnostics instead of throwing
  • Package a generator under analyzers/dotnet/cs, and choose the Roslyn version it references

Before you start

On this page

A CSV writer that works by reflection, like the one of the reflection lesson, allocated 512 bytes for every row it wrote, because it looked up each property and boxed each value while the program ran. A source generator can write the same row code for each type while the program is built: the generated ToCsvRow() of this lesson allocated 152 bytes a row, for the strings of the row alone.

You will write that generator, an incremental one, as a small library that the compiler loads: its project, the pipeline it sets up, the code it writes and the run that uses it. Under every program is the output it gave on .NET 10.

A generator is a netstandard2.0 library that the compiler loads

An incremental source generator: A source generator that implements IIncrementalGenerator: its Initialize method describes a pipeline of steps, and the compiler reruns only the steps whose input changed since the last run. is a class that implements IIncrementalGenerator and carries [Generator], compiled into an ordinary class library. The compiler loads that library into every host it runs in, the command line and each IDE, so the project targets netstandard2.0, and it references the Roslyn package, Microsoft.CodeAnalysis.CSharp 5.9.0, with PrivateAssets="all", because the compiler already brings Roslyn at run time:

csvgen/CsvGen/CsvGen.csproj CSPROJ · csvgen/CsvGen/CsvGen.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <!-- The compiler loads generators into every kind of host, so they target netstandard2.0. -->
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    <!-- The rules for code that runs inside the compiler, and IDE support for debugging the generator. -->
    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
    <IsRoslynComponent>true</IsRoslynComponent>
    <!-- Packing: the .dll goes to analyzers/dotnet/cs, not lib/, and users get it as a build-time tool only. -->
    <Version>1.0.0</Version>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <DevelopmentDependency>true</DevelopmentDependency>
    <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
  </PropertyGroup>

  <ItemGroup>
    <!-- The compiler supplies Roslyn at run time, so the package is not a dependency of the generator's users. -->
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.9.0" PrivateAssets="all" />
  </ItemGroup>

  <ItemGroup>
    <None Include="$(OutputPath)$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
  </ItemGroup>

  <ItemGroup>
    <!-- The checks in ../Checks run the generator on code of their own and may see its internals. -->
    <InternalsVisibleTo Include="Checks" />
  </ItemGroup>

  <ItemGroup>
    <!-- The diagnostics this component reports, release by release (rule RS2008 asks for them). -->
    <AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
    <AdditionalFiles Include="AnalyzerReleases.Unshipped.md" />
  </ItemGroup>

</Project>

EnforceExtendedAnalyzerRules switches on the rules for code that runs inside the compiler, and the two release files list the one diagnostic the generator reports. The project that uses the generator, App, references it with OutputItemType="Analyzer" and ReferenceOutputAssembly="false": the compiler runs it, and the program does not link it. Step through what one build of App does with the two projects:

Interactive · message by message How a build of App runs the generator CsvGen Step through dotnet build in csvgen/App. The build of App is on the left, the compiler in the middle, the generator on the right.
How a build of App runs the generator CsvGen, step 6 of 6. Messages so far: dotnet build App to CsvGen.dll: build CsvGen first; dotnet build App to The C# compiler: CsvGen.dll, as analyzer; The C# compiler to CsvGen.dll: Initialize(context); CsvGen.dll to The C# compiler: added source files; The C# compiler to dotnet build App: App.dll.dotnet build AppMSBuildThe C# compilerApp code plus 4 filesCsvGen.dllCsvGen.dll builtbuild CsvGen firstCsvGen.dll, as analyzerInitialize(context)added source filesApp.dll

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The reference comes first. App.csproj references CsvGen.csproj with OutputItemType="Analyzer", so MSBuild builds CsvGen first, against the Roslyn package it restored.
  2. Handed to the compiler. The reference does not make CsvGen.dll a library of App: ReferenceOutputAssembly="false" keeps it out, and the compiler receives it as an analyzer to load.
  3. Loaded inside the compiler. The compiler loads the assembly and finds the class marked [Generator]. It creates CsvRowGenerator and calls its Initialize method once.
  4. The pipeline is set up. Initialize only describes work: add the marker attribute, find types with [CsvRow], make a model of each, write a file per model. Nothing runs yet.
  5. The pipeline runs. The compiler runs the pipeline over App's code. It finds Booking and Refund, and the generator adds Booking.CsvRow.g.cs and Refund.CsvRow.g.cs.
  6. One assembly. The compiler compiles App's code and the generated files into App.dll. CsvGen.dll is not part of the program: at run time ToCsvRow is plain code of App.

One detail of netstandard2.0 catches nearly every first generator: it predates records and init, which the generator’s model uses, so the project adds a one-line stand-in for the type the compiler needs, in IsExternalInit.cs.

Checkpoint

Check: The generator project and its reference

  1. Question 1 of 2 Why does CsvGen target netstandard2.0 rather than net10.0?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The compiler loads generators into every host it runs in, the command line and each IDE, and netstandard2.0 loads in all of them

    The cost of the old target shows in IsExternalInit.cs: netstandard2.0 lacks the type that records and init accessors need.

  2. Question 2 of 2 App references CsvGen with OutputItemType="Analyzer" and ReferenceOutputAssembly="false". What does that give?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The compiler runs CsvGen as a generator while building App, and App.dll does not reference CsvGen.dll

    The same split applies to a package: the generator goes to analyzers/, never to lib/.

Initialize sets up a pipeline that turns an attribute into a file

A generator does its work in Initialize, and that method only describes it: a chain of steps that the compiler runs whenever the code changes, caching what each step produced. CsvRowGenerator uses the three calls almost every generator needs:

csvgen/CsvGen/CsvRowGenerator.cs C# · csvgen/CsvGen/CsvRowGenerator.cs
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace CsvGen;

/// <summary>Writes CsvHeader and ToCsvRow() for every partial class or record marked [CsvRow].</summary>
[Generator(LanguageNames.CSharp)]
public sealed class CsvRowGenerator : IIncrementalGenerator
{
    internal const string Support = """
        // <auto-generated/>
        #nullable enable
        namespace CsvGen
        {
            [global::Microsoft.CodeAnalysis.EmbeddedAttribute]
            [global::System.AttributeUsage(global::System.AttributeTargets.Class)]
            internal sealed class CsvRowAttribute : global::System.Attribute
            {
            }

            [global::Microsoft.CodeAnalysis.EmbeddedAttribute]
            internal static class CsvText
            {
                private static readonly char[] Special = { ',', '"', '\n', '\r' };

                public static string Field(string? value) =>
                    value is null ? "" :
                    value.IndexOfAny(Special) < 0 ? value :
                    "\"" + value.Replace("\"", "\"\"") + "\"";
            }
        }
        """;

    internal static readonly DiagnosticDescriptor NotPartial = new(
        id: "CSVGEN001",
        title: "A [CsvRow] type must be partial",
        messageFormat: "'{0}' is marked [CsvRow] but is not partial, so no CSV code was generated for it",
        category: "CsvGen",
        defaultSeverity: DiagnosticSeverity.Error,
        isEnabledByDefault: true);

    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 1. Code that every user of the generator needs, added once, before anything else runs.
        context.RegisterPostInitializationOutput(static ctx =>
        {
            ctx.AddEmbeddedAttributeDefinition();
            ctx.AddSource("CsvRow.Support.g.cs", SourceText.From(Support, Encoding.UTF8));
        });

        // 2. Every type with [CsvRow], turned into a model of plain, comparable values.
        IncrementalValuesProvider<RowModel> rows = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "CsvGen.CsvRowAttribute",
                predicate: static (node, _) => node is ClassDeclarationSyntax or RecordDeclarationSyntax,
                transform: static (ctx, _) => RowModel.From((INamedTypeSymbol)ctx.TargetSymbol, (TypeDeclarationSyntax)ctx.TargetNode))
            .WithTrackingName("Rows");

        // 3. One source file per model.
        context.RegisterSourceOutput(rows, static (spc, row) =>
        {
            if (!row.IsPartial)
            {
                spc.ReportDiagnostic(Diagnostic.Create(NotPartial, row.Where.ToLocation(), row.Name));
                return;
            }
            spc.AddSource($"{row.Name}.CsvRow.g.cs", SourceText.From(row.Render(), Encoding.UTF8));
        });
    }
}

/// <summary>What the generator needs to know about one type: strings, booleans and value types only.</summary>
internal sealed record RowModel(string Namespace, string Name, string Keyword, bool IsPartial, EquatableArray<Column> Columns, Place Where)
{
    public static RowModel From(INamedTypeSymbol type, TypeDeclarationSyntax syntax)
    {
        var columns = type.GetMembers()
            .OfType<IPropertySymbol>()
            .Where(p => p.DeclaredAccessibility == Accessibility.Public && !p.IsStatic && !p.IsIndexer && p.GetMethod is not null)
            .Select(p => new Column(p.Name, p.Type.SpecialType == SpecialType.System_String ? Format.Text
                : p.Type.AllInterfaces.Any(i => i.ToDisplayString() == "System.IFormattable") ? Format.Invariant : Format.Plain))
            .ToImmutableArray();
        return new RowModel(
            type.ContainingNamespace.IsGlobalNamespace ? "" : type.ContainingNamespace.ToDisplayString(),
            type.Name,
            type.IsRecord ? "record" : "class",
            syntax.Modifiers.Any(SyntaxKind.PartialKeyword),
            new EquatableArray<Column>(columns),
            Place.Of(syntax.Identifier.GetLocation()));
    }

    public string Render()
    {
        var sb = new StringBuilder();
        sb.AppendLine("// <auto-generated/>");
        sb.AppendLine("#nullable enable");
        if (Namespace.Length > 0) sb.AppendLine($"namespace {Namespace};");
        sb.AppendLine();
        sb.AppendLine($"partial {Keyword} {Name}");
        sb.AppendLine("{");
        sb.AppendLine($"    public static string CsvHeader => \"{string.Join(",", Columns.Select(c => c.Name))}\";");
        sb.AppendLine();
        sb.AppendLine("    public string ToCsvRow() => string.Join(\",\",");
        sb.AppendLine(string.Join(",\n", Columns.Select(c => $"        global::CsvGen.CsvText.Field({c.Expression})")) + ");");
        sb.AppendLine("}");
        return sb.ToString();
    }
}

internal enum Format
{
    Text,
    Invariant,
    Plain,
}

internal sealed record Column(string Name, Format Format)
{
    public string Expression => Format switch
    {
        Format.Text => Name,
        Format.Invariant => $"{Name}.ToString(null, global::System.Globalization.CultureInfo.InvariantCulture)",
        _ => $"{Name}?.ToString()",
    };
}

/// <summary>A source location as values, which compare equal across compilations, unlike a Location object.</summary>
internal readonly record struct Place(string Path, TextSpan Span, LinePositionSpan Lines)
{
    public static Place Of(Location location) => new(location.SourceTree?.FilePath ?? "", location.SourceSpan, location.GetLineSpan().Span);

    public Location ToLocation() => Location.Create(Path, Span, Lines);
}

RegisterPostInitializationOutput adds the marker attribute: An attribute with no behaviour of its own that marks code for a tool, such as [CsvRow] for a source generator. The generator usually adds its definition to the user's project itself. [CsvRow] and a small helper, marked [Embedded] as the Roslyn cookbook advises, so they stay private to each project. ForAttributeWithMetadataName finds every type that carries the attribute, and its transform turns each one into a RowModel of strings and values. RegisterSourceOutput writes one file per model. Step through the pipeline, then a second run after an edit:

Interactive · stage by stage The pipeline CsvRowGenerator sets up, step by step Step through the four steps of Initialize in CsvRowGenerator.cs, then a second run after you edit another file.
The pipeline CsvRowGenerator sets up, step by step, step 6 of 6. Showing post-initialization, find [CsvRow] types, transform to a model, RegisterSourceOutput. In focus: RegisterSourceOutput.equal modelpost-initialization[CsvRow] and CsvTextfind [CsvRow] typesby the attribute's nametransform to a modelRowModel per typeRegisterSourceOutputone file per modelcached

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Before anything else. RegisterPostInitializationOutput adds the [CsvRow] attribute and the CsvText helper once, so the user's code can use the attribute that the rest of the pipeline looks for.
  2. Find the marked types. ForAttributeWithMetadataName asks the compiler for every syntax node with the attribute CsvGen.CsvRowAttribute. The compiler keeps an index of attributes, so most of the code is never looked at.
  3. Make a model. For each class or record found, the transform reads the type's name, namespace and public properties and keeps them in a RowModel of strings and values.
  4. Write a file. RegisterSourceOutput turns each model into C# text and adds it as Booking.CsvRow.g.cs and Refund.CsvRow.g.cs, or reports CSVGEN001 for a type that is not partial.
  5. You edit another file. On the next keystroke the compiler runs the pipeline again. The transform runs again for the marked types, and the new RowModel of Booking is compared with the old one.
  6. Equal, so nothing is written. The models are equal, so the step that writes files is skipped and the old Booking.CsvRow.g.cs is reused. That comparison is why the model must compare by value.

The comparison in the last step is the heart of an incremental generator: the compiler reuses a step’s old output when the new input equals the old, which is why the model holds only values. The session builds App and shows what the generator wrote for Booking:

Building App, the file CsvGen wrote for Booking, and the run Python · build_session.py
from session import run

# network: true. CsvGen references the Roslyn package Microsoft.CodeAnalysis.CSharp, which restore downloads from
# nuget.org into the session's own, empty home folder. Building App builds CsvGen first and runs it as a generator;
# App keeps the generated files under obj/, and the second command shows the one written for Booking.
run("""
cd csvgen/App
dotnet build -v q > build.log
cat obj/Debug/net10.0/generated/CsvGen/CsvGen.CsvRowGenerator/Booking.CsvRow.g.cs
dotnet run --no-build
""", network=True)

Output

$ cd csvgen/App
$ dotnet build -v q > build.log
$ cat obj/Debug/net10.0/generated/CsvGen/CsvGen.CsvRowGenerator/Booking.CsvRow.g.cs
// <auto-generated/>
#nullable enable

partial record Booking
{
    public static string CsvHeader => "Pnr,Train,Coach,Passengers,Fare";

    public string ToCsvRow() => string.Join(",",
        global::CsvGen.CsvText.Field(Pnr),
        global::CsvGen.CsvText.Field(Train.ToString(null, global::System.Globalization.CultureInfo.InvariantCulture)),
        global::CsvGen.CsvText.Field(Coach),
        global::CsvGen.CsvText.Field(Passengers.ToString(null, global::System.Globalization.CultureInfo.InvariantCulture)),
        global::CsvGen.CsvText.Field(Fare.ToString(null, global::System.Globalization.CultureInfo.InvariantCulture)));
}
$ dotnet run --no-build
Pnr,Train,Coach,Passengers,Fare
4521873690,12951,3A,2,1715.25
8834019265,22691,SL,4,1890
Pnr,Amount,Reason
4521873690,857.5,"seat, not given"
reflection writes the same row: True
generated ToCsvRow(): 152 bytes a row
reflection writer:    512 bytes a row

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 build_session.py

The generated ToCsvRow() formats numbers in the invariant culture and quotes only fields that need it, as the refund with a comma shows, and it wrote the same row as the reflection-based writer. The last two lines measure the difference at run time: 152 bytes a row for the generated code, the row’s own strings, against 512 for reflection.

Key idea

A generator’s Initialize describes a pipeline: find the marked types, turn each into a model of plain values, and write a file per model. The compiler runs it on every change and skips the steps whose input has not changed.

Checkpoint

Check: The incremental pipeline of Initialize

  1. Question 1 of 2 Which call of CsvRowGenerator.Initialize adds the [CsvRow] attribute that user code needs?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: RegisterPostInitializationOutput

    Post-initialization output depends on nothing in the user's code, so it is added once and never recomputed.

  2. Question 2 of 2 How many bytes a row did the generated ToCsvRow() allocate in the run of App, where the reflection writer allocated 512?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 152 bytes

    What is left is the row's own strings: the numbers formatted as text and the joined row. The reflection writer also allocated the property array and a box for every number on every row.

Caching that works: equatable models, proved with tracked steps Pro: opens with a Pro, Premium or Ultimate pass

A generator can produce correct code and still slow every keystroke, if its pipeline never caches. This part shows what may go into a model, and measures the difference one wrong choice makes.

In this part:

  • What may go into a model, and why symbols may not
  • One edit, two generators: Cached against Modified, measured
  • Collections in a model, and the equality that keeps them cached

1 runnable example (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Diagnostics instead of exceptions, and tests that run the generator Pro: opens with a Pro, Premium or Ultimate pass

A generator meets every kind of code a user can write, so it must fail politely. This part reproduces what an exception does, reports a problem the right way, and tests both with the driver.

In this part:

  • A generator that throws loses its output, reproduced
  • Reporting CSVGEN001 with a location made of values
  • Tests that run the generator on code of their own

1 runnable example (Python) · about 4 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Packaging a generator, the Roslyn version to reference, and a lab Pro: opens with a Pro, Premium or Ultimate pass

A generator reaches other teams as a NuGet package with a layout of its own. This part packs CsvGen, uses it from a package, explains which Roslyn version to build against, and ends with a lab.

In this part:

  • The package layout: analyzers/dotnet/cs, and nothing in lib
  • Which Roslyn version to reference
  • Lab: an [AutoToString] generator

2 runnable examples (Python) · about 3 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise

Practice that adapts to you

Practise this lesson

8 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 8 Why does CsvGen reference Microsoft.CodeAnalysis.CSharp with PrivateAssets="all"?

    Choose one answer.

    Show the answer: Question 1 of 8

    Answer: The compiler already brings Roslyn at run time, so users of the generator must not get the package as a dependency

    The same reasoning keeps the generator's .dll out of lib/ when it is packed.

  2. Question 2 of 8 What does the generator project target?

    Choose one answer.

    Show the answer: Question 2 of 8

    Answer: netstandard2.0

    The generated code itself is compiled into App, for App's net10.0.

  3. Question 3 of 8 Why does CsvGen contain IsExternalInit.cs?

    Choose one answer.

    Show the answer: Question 3 of 8

    Answer: netstandard2.0 lacks the type that records and init accessors need, and the model is a record

    Without it, the record declarations of the generator fail to compile against netstandard2.0.

  4. Question 4 of 8 A teammate references the generator from App with a plain ProjectReference. What goes wrong?

    Choose one answer.

    Show the answer: Question 4 of 8

    Answer: App references CsvGen.dll as a library, and the compiler does not run it as a generator

    The reference attributes are the difference between a library and a build-time tool.

  5. Question 5 of 8 What does an incremental generator's Initialize method do?

    Choose one answer.

    Show the answer: Question 5 of 8

    Answer: It describes a pipeline of steps, which the compiler runs on every change

    Describing the work, instead of doing it, is what lets the compiler cache and skip steps.

  6. Question 6 of 8 Why does CsvRowGenerator use ForAttributeWithMetadataName instead of looking at every syntax node itself?

    Choose one answer.

    Show the answer: Question 6 of 8

    Answer: The compiler keeps an index of attributes, so most of the code is never looked at

    A marker attribute and ForAttributeWithMetadataName are the cookbook's first advice for any generator.

  7. Question 7 of 8 Why does RowModel hold strings and values rather than the type's symbol?

    Choose one answer.

    Show the answer: Question 7 of 8

    Answer: The compiler compares each model with the previous one to decide whether to rewrite the file

    An equal model is what lets the compiler skip writing the file again.

  8. Question 8 of 8 You write a generator that adds Validate() to classes marked [CheckIfsc], for their string properties. What should its model hold?

    Choose one answer.

    Show the answer: Question 8 of 8

    Answer: The namespace, the class name and the names of the string properties, as a record of values

    Ask what the rendered file needs, and keep exactly that in the model.

Interview questions

Fresher · What makes a source generator incremental?

Its Initialize describes a pipeline of steps instead of doing the work at once. The compiler runs the pipeline on every change, compares each step’s new output with the old one, and skips every later step when they are equal.

  1. Mid to senior

    Since your team added its own source generator, typing in a large solution has become slow, and a trace shows the generator's output being rebuilt on every keystroke for hundreds of types. What do you look at first, and how do you make the generator really incremental?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A build on the CI server shows warning CS8785 from your generator, followed by errors that a generated method does not exist. On your machine everything builds. What happened, and how do you make the generator fail well?

    Model answer, Pro. First step and follow-ups

  3. Senior to staff

    You must ship a source generator to 40 teams that use different SDK and IDE versions. How do you package and version it, which Roslyn version do you build against, and what do you test before every release?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A generator is a netstandard2.0 library that references the Roslyn package privately, and a project runs it through a ProjectReference with OutputItemType=“Analyzer”.
  • Initialize sets up a pipeline: post-initialization output for the marker attribute, ForAttributeWithMetadataName to find the marked types and make a model of each, and RegisterSourceOutput to write a file per model.
  • The compiler reuses a step’s output while its input compares equal, so models hold plain values only.

Terms in this lesson

incremental source generator
A source generator that implements IIncrementalGenerator: its Initialize method describes a pipeline of steps, and the compiler reruns only the steps whose input changed since the last run.
marker attribute
An attribute with no behaviour of its own that marks code for a tool, such as [CsvRow] for a source generator. The generator usually adds its definition to the user's project itself.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.