Källgeneratorer

Den här artikeln innehåller en översikt över källgeneratorer som levereras som en del av .NET Compiler Platform ("Roslyn") SDK. Med källgeneratorer kan C#-utvecklare inspektera användarkoden när den kompileras. Generatorn kan skapa nya C#-källfiler direkt som läggs till i användarens kompilering. På så sätt har du kod som körs under kompilering. Den inspekterar ditt program för att skapa ytterligare källfiler som kompileras tillsammans med resten av koden.

En källgenerator är en ny typ av komponent som C#-utvecklare kan skriva som gör att du kan göra två viktiga saker:

  1. Hämta ett kompileringsobjekt som representerar all användarkod som kompileras. Det här objektet kan inspekteras och du kan skriva kod som fungerar med syntaxen och semantikmodellerna för koden som kompileras, precis som med analysverktyg i dag.

  2. Generera C#-källfiler som kan läggas till i ett kompileringsobjekt under kompilering. Med andra ord kan du ange ytterligare källkod som indata till en kompilering medan koden kompileras.

När de kombineras är dessa två saker det som gör källgeneratorer så användbara. Du kan granska användarkod med alla omfattande metadata som kompilatorn skapar under kompilering. Generatorn genererar sedan C#-kod tillbaka till samma kompilering som baseras på de data som du har analyserat. Om du är bekant med Roslyn Analyzeers kan du betrakta källgeneratorer som analysverktyg som kan generera C#-källkod.

Källgeneratorer körs som en fas av kompilering som visualiseras nedan:

Bild som beskriver de olika delarna av källgenereringen

En källgenerator är en .NET Standard 2.0-sammansättning som läses in av kompilatorn tillsammans med eventuella analysverktyg. Det kan användas i miljöer där .NET Standard-komponenter kan läsas in och köras.

Viktigt

För närvarande kan endast .NET Standard 2.0-sammansättningar användas som källgeneratorer.

Vanliga scenarier

Det finns tre allmänna metoder för att inspektera användarkod och generera information eller kod baserat på den analys som används av tekniker i dag:

  • Körningsreflektion.
  • Jonglera MSBuild-uppgifter.
  • Intermediärt språk (IL) vävning (beskrivs inte i den här artikeln).

Källgeneratorer kan vara en förbättring jämfört med varje metod.

Körningsreflektion

Körningsreflektion är en kraftfull teknik som lades till i .NET för länge sedan. Det finns otaliga scenarier för att använda den. Ett vanligt scenario är att utföra viss analys av användarkod när en app startar och använda dessa data för att generera saker.

Till exempel använder ASP.NET Core reflektion när webbtjänsten först körs för att identifiera konstruktioner som du har definierat så att den kan "koppla upp" saker som kontrollanter och Razor-sidor. Även om det gör att du kan skriva enkel kod med kraftfulla abstraktioner kommer den med en prestandaförsämringar vid körning: när webbtjänsten eller appen startar första gången kan den inte acceptera några begäranden förrän all körningsreflektionskod som identifierar information om koden har körts. Även om det här prestandastraffet inte är enormt är det något av en fast kostnad som du inte kan förbättra dig själv i din egen app.

Med en källgenerator kan identifieringsfasen för kontrollanten i stället ske vid kompilering. En generator kan analysera källkoden och generera den kod som behövs för att "koppla in" din app. Användning av källgeneratorer kan resultera i några snabbare starttider, eftersom en åtgärd som utförs vid körning idag kan skickas till kompileringstiden.

Jonglera MSBuild-uppgifter

Källgeneratorer kan förbättra prestanda på sätt som inte är begränsade till reflektion vid körning för att även identifiera typer. Vissa scenarier omfattar att anropa MSBuild C#-uppgiften (kallas CSC) flera gånger så att de kan inspektera data från en kompilering. Som du kanske föreställer dig påverkar anrop av kompilatorn mer än en gång den totala tid det tar att skapa din app. Vi undersöker hur källgeneratorer kan användas för att dölja behovet av att jonglera MSBuild-uppgifter som detta, eftersom källgeneratorer inte bara erbjuder vissa prestandafördelar, utan även gör det möjligt för verktyg att fungera på rätt abstraktionsnivå.

En annan funktion som källgeneratorer kan erbjuda är att undvika användningen av vissa "strängskrivna" API:er, till exempel hur ASP.NET Core routning mellan kontrollanter och Razor-sidor fungerar. Med en källgenerator kan routning skrivas starkt med de nödvändiga strängar som genereras som en kompileringstidsinformation. Detta skulle minska antalet gånger som en felaktig strängliteral leder till att en begäran inte når rätt kontrollant.

Kom igång med källgeneratorer

I den här guiden utforskar du skapandet av en källgenerator med hjälp av API:et ISourceGenerator .

  1. Skapa ett .NET-konsolprogram. I det här exemplet används .NET 6.

  2. Program Ersätt klassen med följande kod. Följande kod använder inte instruktioner på den översta nivån. Det klassiska formuläret krävs eftersom den första källgeneratorn skriver en partiell metod i den Program klassen:

    namespace ConsoleApp;
    
    partial class Program
    {
        static void Main(string[] args)
        {
            HelloFrom("Generated Code");
        }
    
        static partial void HelloFrom(string name);
    }
    

    Anteckning

    Du kan köra det här exemplet som det är, men inget kommer att hända ännu.

  3. Nu ska vi skapa ett källgeneratorprojekt som implementerar metodens partial void HelloFrom motsvarighet.

  4. Skapa ett .NET-standardbiblioteksprojekt som riktar sig till netstandard2.0 målramverkets moniker (TFM). Lägg till NuGet-paketen Microsoft.CodeAnalysis.Analyzeers och Microsoft.CodeAnalysis.CSharp:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
      </ItemGroup>
    
    </Project>
    

    Tips

    Källgeneratorprojektet måste rikta in sig på netstandard2.0 TFM, annars fungerar det inte.

  5. Skapa en ny C#-fil med namnet HelloSourceGenerator.cs som anger din egen källgenerator så här:

    using Microsoft.CodeAnalysis;
    
    namespace SourceGenerator
    {
        [Generator]
        public class HelloSourceGenerator : ISourceGenerator
        {
            public void Execute(GeneratorExecutionContext context)
            {
                // Code generation goes here
            }
    
            public void Initialize(GeneratorInitializationContext context)
            {
                // No initialization required for this one
            }
        }
    }
    

    En källgenerator måste både implementera Microsoft.CodeAnalysis.ISourceGenerator -gränssnittet och ha Microsoft.CodeAnalysis.GeneratorAttribute. Alla källgeneratorer kräver inte initiering, och så är fallet med den här exempelimplementeringen – där ISourceGenerator.Initialize är tomt.

  6. Ersätt innehållet i ISourceGenerator.Execute metoden med följande implementering:

    using Microsoft.CodeAnalysis;
    
    namespace SourceGenerator
    {
        [Generator]
        public class HelloSourceGenerator : ISourceGenerator
        {
            public void Execute(GeneratorExecutionContext context)
            {
                // Find the main method
                var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);
    
                // Build up the source code
                string source = $@"// <auto-generated/>
    using System;
    
    namespace {mainMethod.ContainingNamespace.ToDisplayString()}
    {{
        public static partial class {mainMethod.ContainingType.Name}
        {{
            static partial void HelloFrom(string name) =>
                Console.WriteLine($""Generator says: Hi from '{{name}}'"");
        }}
    }}
    ";
                var typeName = mainMethod.ContainingType.Name;
    
                // Add the source code to the compilation
                context.AddSource($"{typeName}.g.cs", source);
            }
    
            public void Initialize(GeneratorInitializationContext context)
            {
                // No initialization required for this one
            }
        }
    }
    

    Från - context objektet kan vi komma åt kompileringsstartpunkten eller Main -metoden. Instansen mainMethod är en IMethodSymboloch representerar en metod- eller metodliknande symbol (inklusive konstruktor, destruktor, operator eller egenskap/händelseåtkomst). Metoden Microsoft.CodeAnalysis.Compilation.GetEntryPoint returnerar IMethodSymbol för programmets startpunkt. Med andra metoder kan du hitta valfri metodsymbol i ett projekt. Från det här objektet kan vi resonera om det innehållande namnområdet (om det finns ett) och typen . source I det här exemplet är en interpolerad sträng som mallar källkoden som ska genereras, där de interpolerade hålen fylls med den innehållande namnrymden och typinformationen. source läggs till i context med ett tipsnamn. I det här exemplet skapar generatorn en ny genererad källfil som innehåller en implementering av partial metoden i konsolprogrammet. Du kan skriva källgeneratorer för att lägga till valfri källa.

    Tips

    Parametern hintNameGeneratorExecutionContext.AddSource från metoden kan vara valfritt unikt namn. Det är vanligt att ange ett explicit C#-filnamnstillägg som ".g.cs" eller ".generated.cs" för namnet. Filnamnet hjälper till att identifiera filen som källgenererad.

  7. Nu har vi en fungerande generator, men vi måste ansluta den till konsolprogrammet. Redigera det ursprungliga konsolprogramprojektet och lägg till följande och ersätt projektsökvägen med den från .NET Standard-projektet som du skapade ovan:

    <!-- Add this as a new ItemGroup, replacing paths and names appropriately -->
    <ItemGroup>
        <ProjectReference Include="..\PathTo\SourceGenerator.csproj"
                          OutputItemType="Analyzer"
                          ReferenceOutputAssembly="false" />
    </ItemGroup>
    

    Den här nya referensen är inte en traditionell projektreferens och måste redigeras manuellt för att inkludera attributen OutputItemType och ReferenceOutputAssembly . Mer information om attributen OutputItemTypeProjectReferenceoch ReferenceOutputAssembly för finns i Vanliga MSBuild-projektobjekt: ProjectReference.

  8. När du nu kör konsolprogrammet bör du se att den genererade koden körs och skrivs ut på skärmen. Själva konsolprogrammet implementerar HelloFrom inte -metoden, utan källan som genereras under kompilering från källgeneratorprojektet. Följande text är ett exempel på utdata från programmet:

    Generator says: Hi from 'Generated Code'
    

    Anteckning

    Du kan behöva starta om Visual Studio för att se IntelliSense och bli av med fel när verktygsupplevelsen förbättras aktivt.

  9. Om du använder Visual Studio kan du se de källgenererade filerna. I Solution Explorer-fönstret expanderar du Dependencies>Analyzeers>SourceGenerator>SourceGenerator.HelloSourceGenerator och dubbelklickar på filen Program.g.cs .

    Visual Studio: Källgenererade filer i Solution Explorer.

    När du öppnar den här genererade filen anger Visual Studio att filen genereras automatiskt och att den inte kan redigeras.

    Visual Studio: Automatiskt genererad Program.g.cs-fil.

  10. Du kan också ange byggegenskaper för att spara den genererade filen och styra var de genererade filerna lagras. I konsolprogrammets projektfil lägger du till -elementet <EmitCompilerGeneratedFiles> i en <PropertyGroup>och anger dess värde till true. Skapa projektet igen. Nu skapas de genererade filerna under obj/Debug/net6.0/generated/SourceGenerator/SourceGenerator.HelloSourceGenerator. Komponenterna i sökvägskartan till byggkonfigurationen, målramverket, källgeneratorprojektets namn och det fullständigt kvalificerade typnamnet för generatorn. Du kan välja en enklare utdatamapp genom att lägga till elementet <CompilerGeneratedFilesOutputPath> i programmets projektfil.

Nästa steg

Kokboken Källgeneratorer går igenom några av dessa exempel med några rekommenderade metoder för att lösa dem. Dessutom har vi en uppsättning exempel på GitHub som du kan prova på egen hand.

Du kan läsa mer om källgeneratorer i följande artiklar: