Share via


Brongeneratoren

Dit artikel bevat een overzicht van brongeneratoren die worden geleverd als onderdeel van de .NET Compiler Platform ('Roslyn')-SDK. Met brongeneratoren kunnen C#-ontwikkelaars gebruikerscode inspecteren terwijl deze wordt gecompileerd. De generator kan onmiddellijk nieuwe C#-bronbestanden maken die worden toegevoegd aan de compilatie van de gebruiker. Op deze manier beschikt u over code die wordt uitgevoerd tijdens de compilatie. Het inspecteert uw programma om extra bronbestanden te produceren die samen met de rest van uw code worden gecompileerd.

Een brongenerator is een nieuw soort onderdeel dat C#-ontwikkelaars kunnen schrijven waarmee u twee belangrijke dingen kunt doen:

  1. Haal een compilatieobject op dat alle gebruikerscode vertegenwoordigt die wordt gecompileerd. Dit object kan worden geïnspecteerd en u kunt code schrijven die werkt met de syntaxis en semantische modellen voor de code die wordt gecompileerd, net als bij analysen.

  2. C#-bronbestanden genereren die tijdens de compilatie aan een compilatieobject kunnen worden toegevoegd. Met andere woorden, u kunt aanvullende broncode opgeven als invoer voor een compilatie terwijl de code wordt gecompileerd.

In combinatie zijn deze twee dingen die brongeneratoren zo nuttig maken. U kunt gebruikerscode inspecteren met alle uitgebreide metagegevens die de compiler tijdens de compilatie opbouwt. Uw generator verzendt vervolgens C#-code terug naar dezelfde compilatie die is gebaseerd op de gegevens die u hebt geanalyseerd. Als u bekend bent met Roslyn Analyzers, kunt u brongeneratoren beschouwen als analysen die C#-broncode kunnen verzenden.

Brongeneratoren worden uitgevoerd als een compilatiefase die hieronder wordt gevisualiseerd:

Afbeelding met een beschrijving van de verschillende onderdelen van het genereren van de bron

Een brongenerator is een .NET Standard 2.0-assembly die wordt geladen door de compiler, samen met eventuele analyseprogramma's. Het is bruikbaar in omgevingen waarin .NET Standard-onderdelen kunnen worden geladen en uitgevoerd.

Belangrijk

Momenteel kunnen alleen .NET Standard 2.0-assembly's worden gebruikt als brongeneratoren.

Algemene scenario's

Er zijn drie algemene benaderingen voor het inspecteren van gebruikerscode en het genereren van informatie of code op basis van de analyse die tegenwoordig door technologieën wordt gebruikt:

  • Runtime-reflectie.
  • MsBuild-taken jongleren.
  • Tussenliggende taal (IL) weven (niet besproken in dit artikel).

Brongeneratoren kunnen een verbetering zijn ten opzichte van elke benadering.

Runtime-reflectie

Runtime-reflectie is een krachtige technologie die lang geleden aan .NET is toegevoegd. Er zijn talloze scenario's voor het gebruik ervan. Een veelvoorkomend scenario is om een analyse van gebruikerscode uit te voeren wanneer een app wordt gestart en die gegevens te gebruiken om dingen te genereren.

ASP.NET Core gebruikt bijvoorbeeld reflectie wanneer uw webservice voor het eerst wordt uitgevoerd om constructies te detecteren die u hebt gedefinieerd, zodat deze zaken zoals controllers en scheerpagina's kan 'verbinden'. Hoewel u hiermee eenvoudige code met krachtige abstracties kunt schrijven, wordt er een prestatievermindering toegepast tijdens uitvoering: wanneer uw webservice of app voor het eerst wordt gestart, kan deze geen aanvragen accepteren totdat alle runtimereflectiecode die informatie over uw code detecteert, is uitgevoerd. Hoewel deze prestatievermindering niet enorm is, zijn het een beetje vaste kosten die u zelf niet kunt verbeteren in uw eigen app.

Met een brongenerator kan de detectiefase van de controller tijdens het opstarten plaatsvinden tijdens het compileren. Een generator kan uw broncode analyseren en de code verzenden die nodig is om uw app te 'bedraden'. Het gebruik van brongeneratoren kan leiden tot een aantal snellere opstarttijden, omdat een actie die momenteel tijdens runtime plaatsvindt, kan worden gepusht naar de compilatietijd.

MsBuild-taken jongleren

Brongeneratoren kunnen de prestaties verbeteren op manieren die niet beperkt zijn tot reflectie tijdens runtime om ook typen te detecteren. In sommige scenario's wordt de MSBuild C#-taak (CSC genoemd) meerdere keren aangeroepen, zodat ze gegevens uit een compilatie kunnen inspecteren. Zoals u zich misschien kunt voorstellen, heeft het meerdere keren aanroepen van de compiler invloed op de totale tijd die nodig is om uw app te bouwen. We onderzoeken hoe brongeneratoren kunnen worden gebruikt om de noodzaak voor het jongleren van MSBuild-taken als deze te omzeilen, omdat brongenerators niet alleen enkele prestatievoordelen bieden, maar ook hulpprogramma's in staat stellen om op het juiste abstractieniveau te werken.

Een andere mogelijkheid die brongeneratoren kunnen bieden, is het gebruik van sommige 'stringly typed' API's, zoals hoe ASP.NET Core routering tussen controllers en razor-pagina's werken. Met een brongenerator kan routering sterk worden getypeerd met de benodigde tekenreeksen die worden gegenereerd als een compileertijddetail. Dit vermindert het aantal keren dat een verkeerd getypte letterlijke tekenreeks ertoe leidt dat een aanvraag niet de juiste controller raakt.

Aan de slag met brongeneratoren

In deze handleiding verkent u het maken van een brongenerator met behulp van de ISourceGenerator API.

  1. Maak een .NET-consoletoepassing. In dit voorbeeld wordt .NET 6 gebruikt.

  2. Vervang de Program klasse door de volgende code. De volgende code gebruikt geen instructies op het hoogste niveau. De klassieke vorm is vereist omdat deze eerste brongenerator een gedeeltelijke methode in die Program klasse schrijft:

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

    Notitie

    U kunt dit voorbeeld uitvoeren zoals het is, maar er gebeurt nog niets.

  3. Vervolgens maken we een brongeneratorproject waarmee de partial void HelloFrom methode-tegenhanger wordt geïmplementeerd.

  4. Maak een .NET-standaardbibliotheekproject dat is gericht op de netstandard2.0 doelframework moniker (TFM). Voeg de NuGet-pakketten Microsoft.CodeAnalysis.Analyzers en Microsoft.CodeAnalysis.CSharp toe:

    <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>
    

    Tip

    Het brongeneratorproject moet gericht zijn op de netstandard2.0 TFM, anders werkt het niet.

  5. Maak een nieuw C#-bestand met de naam HelloSourceGenerator.cs waarin uw eigen brongenerator als volgt wordt opgegeven:

    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
            }
        }
    }
    

    Een brongenerator moet zowel de Microsoft.CodeAnalysis.ISourceGenerator interface implementeren als de Microsoft.CodeAnalysis.GeneratorAttributehebben. Niet alle brongeneratoren vereisen initialisatie, en dat is het geval met deze voorbeeld-implementatie, waarbij ISourceGenerator.Initialize leeg is.

  6. Vervang de inhoud van de ISourceGenerator.Execute methode door de volgende implementatie:

    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
            }
        }
    }
    

    Vanuit het context -object hebben we toegang tot het toegangspunt of Main de methode van de compilaties. Het mainMethod exemplaar is een IMethodSymbolen vertegenwoordigt een methode of methode-achtig symbool (inclusief constructor, destructor, operator of eigenschap/gebeurtenis-accessor). De Microsoft.CodeAnalysis.Compilation.GetEntryPoint methode retourneert de IMethodSymbol voor het toegangspunt van het programma. Met andere methoden kunt u elk methodesymbool in een project vinden. Vanuit dit object kunnen we redeneringen geven over de naamruimte die de naamruimte bevat (als deze aanwezig is) en het type. De source in dit voorbeeld is een geïnterpoleerde tekenreeks waarmee de broncode wordt gebruikt die moet worden gegenereerd, waarbij de geïnterpoleerde gaten worden opgevuld met de naamruimte en typegegevens. De source wordt toegevoegd aan de context met een hintnaam. Voor dit voorbeeld maakt de generator een nieuw gegenereerd bronbestand dat een implementatie van de partial methode in de consoletoepassing bevat. U kunt brongeneratoren schrijven om elke gewenste bron toe te voegen.

    Tip

    De hintName parameter van de GeneratorExecutionContext.AddSource methode kan elke unieke naam zijn. Het is gebruikelijk om een expliciete C#-bestandsextensie op te geven, zoals ".g.cs" of ".generated.cs" voor de naam. De bestandsnaam helpt bij het identificeren van het bestand als bron gegenereerd.

  7. We hebben nu een werkende generator, maar moeten deze verbinden met onze consoletoepassing. Bewerk het oorspronkelijke consoletoepassingsproject en voeg het volgende toe, waarbij u het projectpad vervangt door het pad van het .NET Standard-project dat u hierboven hebt gemaakt:

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

    Deze nieuwe verwijzing is geen traditionele projectreferentie en moet handmatig worden bewerkt om de OutputItemType kenmerken en ReferenceOutputAssembly op te nemen. Zie Algemene MSBuild-projectitems: ProjectReference voor meer informatie over de OutputItemType kenmerken en ReferenceOutputAssembly van ProjectReference.

  8. Wanneer u nu de consoletoepassing uitvoert, ziet u dat de gegenereerde code wordt uitgevoerd en op het scherm wordt afgedrukt. De consoletoepassing zelf implementeert de HelloFrom methode niet, maar de bron die wordt gegenereerd tijdens de compilatie van het brongeneratorproject. De volgende tekst is een voorbeeld van uitvoer van de toepassing:

    Generator says: Hi from 'Generated Code'
    

    Notitie

    Mogelijk moet u Visual Studio opnieuw starten om IntelliSense te zien en fouten te verwijderen, omdat de ervaring met hulpprogramma's actief wordt verbeterd.

  9. Als u Visual Studio gebruikt, kunt u de door de bron gegenereerde bestanden zien. Vouw in het venster Solution Explorer de dependencies>Analyzers>SourceGenerator>SourceGenerator.HelloSourceGenerator uit en dubbelklik op het bestand Program.g.cs .

    Visual Studio: door Solution Explorer gegenereerde bronbestanden.

    Wanneer u dit gegenereerde bestand opent, geeft Visual Studio aan dat het bestand automatisch wordt gegenereerd en dat het niet kan worden bewerkt.

    Visual Studio: automatisch gegenereerd Program.g.cs-bestand.

  10. U kunt ook build-eigenschappen instellen om het gegenereerde bestand op te slaan en te bepalen waar de gegenereerde bestanden worden opgeslagen. Voeg in het projectbestand van de consoletoepassing het element toe aan een <PropertyGroup>en stel de <EmitCompilerGeneratedFiles> waarde ervan in op true. Bouw uw project opnieuw op. De gegenereerde bestanden worden nu gemaakt onder obj/Debug/net6.0/generated/SourceGenerator/SourceGenerator.HelloSourceGenerator. De onderdelen van het pad worden toegewezen aan de buildconfiguratie, het doelframework, de naam van het brongeneratorproject en de volledig gekwalificeerde typenaam van de generator. U kunt een handigere uitvoermap kiezen door het <CompilerGeneratedFilesOutputPath> element toe te voegen aan het projectbestand van de toepassing.

Volgende stappen

In het Kookboek voor brongeneratoren worden enkele van deze voorbeelden beschreven met een aantal aanbevolen benaderingen om ze op te lossen. Daarnaast hebben we een reeks voorbeelden beschikbaar op GitHub die u zelf kunt proberen.

Meer informatie over brongeneratoren vindt u in de volgende artikelen: