ใช้ Visual Studio และ Source Generator เพื่อสร้างโค้ดโดยอัตโนมัติ

ปรับปรุงหน้า :
วันที่สร้างเพจ :

สภาพแวดล้อมในการทํางาน

วิชวลสตูดิโอ
  • วิชวลสตูดิโอ 2022
ตาข่าย
  • .NET 8.0

เบื้องต้น

วิชวลสตูดิโอ
  • ใช้งานได้แม้กับเวอร์ชันที่ค่อนข้างเก่า
ตาข่าย
  • ใช้งานได้แม้กับเวอร์ชันที่ค่อนข้างเก่า

ทีแรก

มีเทคนิคหลายอย่างในการสร้างโค้ดโดยอัตโนมัติด้วยคําจํากัดความของคุณเอง แต่ในบทความนี้ฉันจะแสดงวิธีใช้ Source Generator ข้อดีอย่างหนึ่งที่ใหญ่ที่สุดของตัวสร้างแหล่งที่มาคือการวิเคราะห์โครงสร้างของซอร์สโค้ดของโครงการปัจจุบันและสร้างโค้ดใหม่ตามนั้น ตัวอย่างเช่น เมื่อคุณสร้างคลาสใหม่ คุณสามารถสร้างคลาสนั้นเพื่อให้โค้ดถูกเพิ่มโดยอัตโนมัติเพื่อให้ตรงกับคลาส คุณสามารถตั้งโปรแกรมประเภทของโค้ดที่คุณต้องการสร้าง เพื่อให้คุณสามารถสร้างรูปแบบการสร้างโค้ดอัตโนมัติที่คุณชอบได้

รหัสนี้สร้างขึ้นโดยอัตโนมัติในพื้นหลังและรวมเข้ากับโครงการเบื้องหลัง เนื่องจากไม่ได้ส่งออกเป็นไฟล์ในลักษณะที่มองเห็นได้ จึงไม่ได้ใช้เพื่อวัตถุประสงค์ในการนําโค้ดที่สร้างขึ้นโดยอัตโนมัติกลับมาใช้ใหม่เพื่อวัตถุประสงค์ทั่วไป (แม้ว่าจะสามารถลบออกได้โดยการคัดลอกในขณะนี้) อย่างไรก็ตาม เนื่องจากโค้ดถูกสร้างขึ้นโดยอัตโนมัติตามโครงสร้างของโครงการ จึงช่วยลดต้นทุนในการป้อนข้อมูลด้วยตนเองและลดข้อผิดพลาดในการเขียนโค้ดตามนั้น ซึ่งเป็นข้อได้เปรียบอย่างมาก

ในบทความนี้ฉันจะอธิบายวิธีตรวจสอบว่ารหัสถูกสร้างขึ้นโดยอัตโนมัติดังนั้นฉันจะไม่ไปไกลถึงการวิเคราะห์โค้ดอย่างลึกซึ้งและดําเนินการเอาต์พุตขั้นสูง โปรดค้นหาด้วยตัวคุณเองเป็นแอปพลิเคชัน

ตั้ง ค่า

ขั้นแรก ให้ติดตั้ง Visual Studio คําอธิบายสั้น ๆ สรุปไว้ในเคล็ดลับต่อไปนี้

โดยทั่วไปคุณสามารถใช้มันสําหรับโครงการใด ๆ ดังนั้นจึงไม่สําคัญว่าคุณจะตั้งค่าปริมาณงานใด อย่างไรก็ตามคราวนี้เป็น "องค์ประกอบส่วนบุคคล" " NET Compiler Platform SDK สิ่งนี้มีประโยชน์สําหรับการดีบักระหว่างการพัฒนา Source Generator ถ้าคุณติดตั้ง Visual Studio ไว้แล้ว คุณสามารถเพิ่มได้จากเมนู Visual Studio ภายใต้ เครื่องมือ > รับเครื่องมือและคุณลักษณะ

การสร้างและเตรียมโครงการสร้างแหล่งที่มา

Source Generator ถูกสร้างขึ้นในโครงการแยกต่างหากจากโครงการแอปพลิเคชันหลัก ไม่สําคัญว่าคุณจะสร้างก่อนหรือสร้างเพิ่มเติมในภายหลัง ในกรณีนี้ฉันจะสร้างจากโครงการ Source Generator

บนหน้าจอ Create New Project ให้เลือก Class Library

ชื่อโครงการสามารถเป็นอะไรก็ได้ แต่สําหรับตอนนี้ CodeGenerator เราจะปล่อยให้เป็น .

สําหรับไลบรารีคลาส Source Generator รองรับในขณะนี้ มาตรฐานสุทธิ 2.0

เมื่อคุณสร้างโปรเจ็กต์แล้ว ให้รับแพ็คเกจด้วย Microsoft.CodeAnalysis.CSharp NuGet ลักษณะการทํางานอาจแตกต่างกันไปขึ้นอยู่กับเวอร์ชัน แต่ไม่มีประโยชน์ที่จะใช้เวอร์ชันเก่าต่อไปดังนั้นฉันจะใส่เวอร์ชันล่าสุด

จากนั้นเปิดไฟล์โครงการเป็นรหัส

เมื่อคุณเปิดคุณจะเห็นสิ่งต่อไปนี้

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
  </ItemGroup>

</Project>

เพิ่มเป็นดังนี้: อย่าลังเลที่จะเพิ่มสิ่งอื่นที่คุณต้องการ

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>

    <!-- Roslyn によるコード解析を行う -->
    <IsRoslynComponent>true</IsRoslynComponent>
    <!-- 入れないと警告が表示されるので入れておく -->
    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
    <!-- 最新の C# の記述を使いたいので入れておく -->
    <LangVersion>Latest</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
  </ItemGroup>

</Project>

จากนั้นเขียนโค้ดสําหรับคลาสที่จะสร้างโค้ดโดยอัตโนมัติ ก่อนอื่น เราจะสร้างเฟรมเวิร์กเท่านั้น ดังนั้นโปรดเขียนโค้ดที่มีอยู่ใหม่ตั้งแต่ต้น Class1.cs หรือเพิ่มโค้ดใหม่

รหัสควรมีลักษณะดังนี้: ชื่อคลาสสามารถเป็นอะไรก็ได้ แต่จะดีกว่าถ้ามีชื่อที่แสดงประเภทของรหัสที่สร้างขึ้นโดยอัตโนมัติ SampleGenerator สําหรับตอนนี้ปล่อยให้เป็น . Initialize ในวิธีนี้ คุณจะเขียนกระบวนการวิเคราะห์โค้ดและการสร้างโค้ด

using Microsoft.CodeAnalysis;

namespace CodeGenerator
{
  [Generator(LanguageNames.CSharp)]
  public partial class SampleGenerator : IIncrementalGenerator
  {
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
    }
  }
}

จากนั้นสร้างโครงการสําหรับแอปพลิเคชันที่คุณต้องการสร้างโค้ดโดยอัตโนมัติ มันสามารถเป็นอะไรก็ได้ แต่ที่นี่เราจะใช้เป็นแอปพลิเคชันคอนโซล ประเภทและเวอร์ชันของเฟรมเวิร์กของโครงการที่สร้างโค้ดโดยอัตโนมัติสอดคล้องกับหมายเลขทั่วไป

เพิ่มการอ้างอิงไปยังโครงการตัวสร้างต้นทางจากโครงการฝั่งแอปพลิเคชัน

ไม่สามารถตั้งค่าบางอย่างในคุณสมบัติได้ดังนั้นให้เปิดไฟล์โครงการในโค้ด

ฉันคิดว่ามันมีลักษณะดังนี้:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\CodeGenerator\CodeGenerator.csproj" />
  </ItemGroup>

</Project>

เพิ่มการตั้งค่าสําหรับโครงการที่อ้างอิงดังนี้: ตรวจสอบให้แน่ใจว่าคุณไม่ได้ทําผิดพลาดในไวยากรณ์ของ XML

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\CodeGenerator\CodeGenerator.csproj">
      <OutputItemType>Analyzer</OutputItemType>
      <ReferenceOutputAssembly>False</ReferenceOutputAssembly>
    </ProjectReference>
  </ItemGroup>

</Project>

จากนั้นเปิดคุณสมบัติโครงการที่ "ด้านตัวสร้างโค้ด"

คลิกลิงก์เพื่อเปิด UI คุณสมบัติการเปิดใช้การตรวจแก้จุดบกพร่อง

ลบโปรไฟล์เดิมเนื่องจากคุณไม่ต้องการใช้

เพิ่มโปรไฟล์ใหม่

เลือก Roslyn Component

หากคุณได้ทําการตั้งค่าจนถึงตอนนี้คุณควรจะสามารถเลือกโครงการแอปพลิเคชันได้ดังนั้นให้เลือก

นี่คือจุดสิ้นสุดของการเตรียมการ

ตรวจสอบว่าคุณสามารถแก้ไขข้อบกพร่องได้หรือไม่

เปิดรหัสตัวสร้างต้นทางและวาง Initialize เบรกพอยต์ที่ส่วนท้ายของวิธีการ

มาดีบักตัวสร้างแหล่งที่มากันเถอะ

หากกระบวนการหยุดที่เบรกพอยต์คุณสามารถยืนยันได้ว่าคุณกําลังดีบักตามปกติ สิ่งนี้จะทําให้การพัฒนาตัวสร้างต้นทางของคุณง่ายพอสมควร

ในตอนนี้ เรามาส่งออกรหัสคงที่กัน

ก่อนอื่นเรามาลองส่งออกรหัสคงที่ได้อย่างง่ายดาย ง่ายเพราะคุณไม่จําเป็นต้องวิเคราะห์โค้ดด้วยซ้ํา แม้ว่าจะเป็นรหัสคงที่ แต่ก็ได้รับการจัดการเป็นสตริงดังนั้นจึงเป็นไปได้ที่จะเพิ่มการผลิตรหัสในรูปแบบคงที่โดยการสร้างโปรแกรม

หากคุณต้องการส่งออกรหัสคงที่คุณสามารถทําได้ context.RegisterPostInitializationOutput โดยใช้ ต่อไปนี้เป็นตัวอย่างของเอาต์พุตโค้ด

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

namespace CodeGenerator;

[Generator(LanguageNames.CSharp)]
public partial class SampleGenerator : IIncrementalGenerator
{
  public void Initialize(IncrementalGeneratorInitializationContext context)
  {
    // コードを解析せずそのままコードを出力するならこれを使用します
    context.RegisterPostInitializationOutput(static postInitializationContext =>
    {
      // コード生成処理が中断されることもあるので CancellationToken 処理は入れたほうがいいです
      System.Threading.CancellationToken token = postInitializationContext.CancellationToken;
      token.ThrowIfCancellationRequested();

      // 出力するコードを作ります
      var source = """
internal static class SampleClass
{
  public static void Hello() => Console.WriteLine("Hello Source Generator!!");
}
""";
      
      // コードファイルに出力します (実際に目に見えるどこかのフォルダに出力されるわけではありません)
      postInitializationContext.AddSource("SampleGeneratedFile.cs", SourceText.From(source, Encoding.UTF8));
    });
  }
}

เนื้อหาของรหัสเป็นไปตามที่เขียนไว้ในความคิดเห็นดังนั้นฉันจะละเว้นรายละเอียด สร้างและตรวจสอบให้แน่ใจว่าไม่มีข้อผิดพลาด

เมื่อบิลด์เสร็จสมบูรณ์ คุณสามารถขยาย "ตัววิเคราะห์" ในโครงการแอปพลิเคชันเพื่อดูโค้ดที่สร้างโดยตัวสร้างโค้ด

หากคุณไม่เห็น ให้รีสตาร์ท Visual Studio และตรวจสอบ ดูเหมือนว่า ในเวลานี้ Visual Studio อาจไม่ได้รับการอัปเดตเว้นแต่คุณจะรีสตาร์ท การเน้น Intellisence และไวยากรณ์ที่ใช้เมื่อสร้างโปรแกรมมีความคล้ายคลึงกัน อย่างไรก็ตามเนื่องจากโค้ดนั้นสะท้อนให้เห็นในขณะที่สร้างดูเหมือนว่าโปรแกรมจะสะท้อนให้เห็นในด้านแอปพลิเคชัน

เมื่อสร้างรหัสแล้ว ให้ลองใช้ในแอปพลิเคชันของคุณ มันควรจะทํางานได้ดี

วิเคราะห์และสร้างโค้ด

หากคุณเพียงแค่ประมวลผลโค้ดตามปกติและส่งออก ก็ไม่แตกต่างจากการสร้างโค้ดอัตโนมัติอื่นๆ มากนัก และคุณไม่สามารถใช้ประโยชน์จากประโยชน์ของตัวสร้างแหล่งที่มาได้ ตอนนี้เรามาวิเคราะห์รหัสของโครงการแอปพลิเคชันและสร้างรหัสตามนั้น

อย่างไรก็ตาม การวิเคราะห์โค้ดค่อนข้างลึก ดังนั้นฉันจึงไม่สามารถอธิบายทุกอย่างได้ที่นี่ ในตอนนี้ฉันจะอธิบายจนถึงจุดที่คุณสามารถวิเคราะห์และส่งออกโค้ดได้ หากคุณต้องการเจาะลึกลงไปโปรดทําวิจัยของคุณเอง

ในขณะนี้ตัวอย่างเช่นฉันต้องการสร้างรหัส "เพิ่มวิธีการให้กับ Reset คลาสที่สร้างขึ้นทั้งหมดโดยอัตโนมัติและรีเซ็ตค่า default ของคุณสมบัติทั้งหมดเป็น " ในช่วงไม่กี่ปีที่ผ่านมาดูเหมือนว่าการจัดการอินสแตนซ์ที่ไม่เปลี่ยนรูปเป็นที่ต้องการ แต่ในโปรแกรมเกมไม่สามารถสร้างอินสแตนซ์ใหม่ได้ทุกเฟรมดังนั้นฉันคิดว่ามีประโยชน์สําหรับกระบวนการรีเซ็ตค่า default คุณอาจต้องการตั้งค่าอย่างอื่นนอกเหนือจากนั้น แต่ถ้าคุณทําเช่นนั้น มันจะเป็นตัวอย่างแรกที่ยาว ดังนั้นโปรดนําไปใช้ด้วยตัวเอง

ด้านตัวสร้างโค้ด

สร้างคลาสใหม่เพื่อจุดประสงค์ในการเก็บตัวสร้างที่คุณสร้างไว้ก่อนหน้านี้ หากเนื้อหาที่จะสร้างโดยอัตโนมัติมีการเปลี่ยนแปลงจะเป็นการดีกว่าที่จะสร้างเนื้อหาใหม่ในชั้นเรียนอื่น

คอมไพเลอร์ Roslyn ดูแลโครงสร้างของโค้ด ดังนั้นสิ่งที่เราจะทําที่นี่คือแยกวิเคราะห์ข้อมูลที่มีโครงสร้างและเขียนโค้ด

ก่อนอื่นฉันจะโพสต์รหัสทั้งหมด ฉันพยายามมีโค้ดให้น้อยที่สุด คราวนี้ใช้งานได้ แต่อยู่ในระดับที่เคลื่อนไหวในฐานะ Mr./Ms. เท่านั้น ดังนั้นเมื่อคุณดําเนินการพัฒนาจริง จะมีส่วนที่ขาดหายไปค่อนข้างน้อย โปรดปรับปรุงที่นั่นตามความจําเป็น

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;
using System.Text;
using System.Threading;

namespace CodeGenerator;

[Generator(LanguageNames.CSharp)]
public partial class Sample2Generator : IIncrementalGenerator
{
  public void Initialize(IncrementalGeneratorInitializationContext context)
  {
    // 構文を解析し処理対象のノードをコード出力用に変換します
    var syntaxProvider = context.SyntaxProvider.CreateSyntaxProvider(
      // すべてのノードが処理対象となるため、predicate で処理対象とするノードを限定します
      predicate: static (node, cancelToken) =>
      {
        // 処理が中断される場面は多々あるのでいつでもキャンセルできるようにしておく
        cancelToken.ThrowIfCancellationRequested();

        // クラスのノードのみを対象とする (record とかつけると別な種類のノードになるので注意)
        // また partial class であること
        return node is ClassDeclarationSyntax nodeClass && nodeClass.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
      },
      // 対象のノードをコード出力に必要な形に変換します
      transform: static (contextSyntax, cancelToken) =>
      {
        // いつでもキャンセルできるようにしておく
        cancelToken.ThrowIfCancellationRequested();

        // 今回は ClassDeclarationSyntax 確定なのでそのままキャストします
        ClassDeclarationSyntax classDecl = (ClassDeclarationSyntax)contextSyntax.Node;

        // データと構文から宣言されたシンボルを取得します
        // 今回利用場面がほとんどないですが取得しておくといろんな情報を取得できるので便利です
        var symbol = Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(contextSyntax.SemanticModel, classDecl, cancelToken)!;

        // 出力に必要な値を返します。大抵の型を渡すことができます
        return (symbol, classDecl);
      }
    );

    // 解析し変換した情報をもとにコードを出力します
    context.RegisterSourceOutput(syntaxProvider, static (contextSource, input) =>
    {
      // いつでもキャンセルできるようにしておく
      CancellationToken cancelToken = contextSource.CancellationToken;
      cancelToken.ThrowIfCancellationRequested();

      // 解析後に渡された値を受け取ります
      var (symbol, classDecl) = input;

      StringBuilder sbInitValues = new();

      // プロパティを列挙して値を初期化するコードを生成
      foreach (var syntax in classDecl.Members.OfType<PropertyDeclarationSyntax>())
      {
        sbInitValues.AppendLine($"{syntax.Identifier.ValueText} = default;");
      }

      // 出力するコードを作成
      var source = @$"
        {string.Join(" ", classDecl.Modifiers.Select(x => x.ToString()))} class {symbol.Name}
        {{
          public void Reset()
          {{
            {sbInitValues}
          }}
        }}";

      // ファイル名はクラス名に合わせておきます。作成したコードを出力します
      contextSource.AddSource($"{symbol.Name}.g.cs", source);
    });
  }
}

ฉันได้เขียนเกี่ยวกับเรื่องนี้ในความคิดเห็น แต่ฉันจะอธิบายในบางสถานที่

// 構文を解析し処理対象のノードをコード出力用に変換します
var syntaxProvider = context.SyntaxProvider.CreateSyntaxProvider(
  // すべてのノードが処理対象となるため、predicate で処理対象とするノードを限定します
  predicate: ...,
  // 対象のノードをコード出力に必要な形に変換します
  transform: ...
);

context.SyntaxProvider.CreateSyntaxProvider เพื่อวิเคราะห์โค้ดในโครงการแอปพลิเคชันและจัดโครงสร้างให้มากที่สุด ทุกอย่างแบ่งออกเป็นโหนดและเราใช้เพื่อกําหนดว่าเราต้องการ predicate ประมวลผลโหนดใด นอกจากนี้ หากจําเป็น transform ให้แปลงวัตถุที่ประมวลผลแล้วส่งต่อไปยังด้านเอาต์พุต

predicateในกรณีนี้เรากําลังดําเนินการดังต่อไปนี้

// すべてのノードが処理対象となるため、predicate で処理対象とするノードを限定します
predicate: static (node, cancelToken) =>
{
  // 処理が中断される場面は多々あるのでいつでもキャンセルできるようにしておく
  cancelToken.ThrowIfCancellationRequested();

  // クラスのノードのみを対象とする (record とかつけると別な種類のノードになるので注意)
  // また partial class であること
  return node is ClassDeclarationSyntax nodeClass && nodeClass.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
},

ก่อนอื่นเช่นเดียวกับกระบวนการใด ๆ กระบวนการสร้างรหัสโดยอัตโนมัติสามารถยกเลิกก่อนเวลาอันควรได้เสมอ ดังนั้นใช้โทเค็นการยกเลิกเพื่อ ThrowIfCancellationRequested โทรเพื่อให้คุณสามารถขัดจังหวะได้ตลอดเวลา

predicate ตอนนี้มีการเรียกทุกโหนดแล้วเราต้องการพิจารณาว่าโหนดใดเป็นโหนดที่เราต้องการประมวลผล เนื่องจากมีจํานวนมากจึงควร จํากัด ให้แคบลงที่นี่ในระดับหนึ่ง

เนื่องจากเราจะเพิ่มการประมวลผลในชั้นเรียนในครั้งนี้เราจะ ClassDeclarationSyntax พิจารณาว่าเป็นเช่นนั้นหรือไม่และพิจารณาว่าจะมีการประมวลผลเฉพาะชั้นเรียนหรือไม่ partial class นอกจากนี้เนื่องจากรหัสแนบมาด้วย partial class จึงถือเป็นการตัดสิน

// 対象のノードをコード出力に必要な形に変換します
transform: static (contextSyntax, cancelToken) =>
{
  // いつでもキャンセルできるようにしておく
  cancelToken.ThrowIfCancellationRequested();

  // 今回は ClassDeclarationSyntax 確定なのでそのままキャストします
  ClassDeclarationSyntax classDecl = (ClassDeclarationSyntax)contextSyntax.Node;

  // データと構文から宣言されたシンボルを取得します
  // 今回利用場面がほとんどないですが取得しておくといろんな情報を取得できるので便利です
  var symbol = Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(contextSyntax.SemanticModel, classDecl, cancelToken)!;

  // 出力に必要な値を返します。大抵の型を渡すことができます
   return (symbol, classDecl);
}

transform ช่วยให้คุณสามารถแปลงการวิเคราะห์เป็นรูปแบบที่ต้องการและส่งต่อไปยังเอาต์พุตโค้ด คราวนี้เราไม่ได้ทําการแปลงมากนัก แต่ส่งผ่านค่าไปยังด้านเอาต์พุตตามที่เป็น return อยู่ เราได้รับ "สัญลักษณ์ที่ประกาศ" ไปพร้อมกัน แต่SemanticModel เราสามารถรับข้อมูลเพิ่มเติมมากมายโดยใช้ และ Syntac ดังนั้นฉันคิดว่าเราสามารถรับได้หากจําเป็น return ทูเปิลถูกสร้างขึ้นและส่งคืน แต่การกําหนดค่าของข้อมูลที่จะส่งผ่านไปยังด้านเอาต์พุตอาจเป็นอะไรก็ได้ หากคุณต้องการส่งข้อมูลหลายรายการคุณสามารถสร้างทูเพิลเช่นนี้หรือคุณสามารถกําหนดคลาสของคุณเองและส่งต่อได้

// 解析し変換した情報をもとにコードを出力します
context.RegisterSourceOutput(syntaxProvider, static (contextSource, input) =>
{
  // いつでもキャンセルできるようにしておく
  CancellationToken cancelToken = contextSource.CancellationToken;
  cancelToken.ThrowIfCancellationRequested();

  // 解析後に渡された値を受け取ります
  var (symbol, classDecl) = input;

  StringBuilder sbInitValues = new();

  // プロパティを列挙して値を初期化するコードを生成
  foreach (var syntax in classDecl.Members.OfType<PropertyDeclarationSyntax>())
  {
    sbInitValues.AppendLine($"{syntax.Identifier.ValueText} = default;");
  }

  // 出力するコードを作成
  var source = @$"
    {string.Join(" ", classDecl.Modifiers.Select(x => x.ToString()))} class {symbol.Name}
    {{
      public void Reset()
      {{
        {sbInitValues}
      }}
    }}";

  // ファイル名はクラス名に合わせておきます。作成したコードを出力します
  contextSource.AddSource($"{symbol.Name}.g.cs", source);
});

context.RegisterSourceOutputcontext.SyntaxProvider.CreateSyntaxProviderตอนนี้เราจะสร้างและส่งออกรหัสตามข้อมูลที่จะประมวลผลโดย context.SyntaxProvider.CreateSyntaxProvider returnค่าของ สามารถรับเป็นอาร์กิวเมนต์ที่สองของ Action ดังนั้นรหัสจะถูกสร้างขึ้นตามค่านั้น

ในกรณีนี้เรากําลังสร้างโค้ดเพื่อรับรายการไวยากรณ์ของคุณสมบัติจากไวยากรณ์ของคลาสและตั้งค่า default คุณสมบัติเป็นจากแต่ละชื่อ

หลังจากนั้น ให้สร้างเมธอดตาม Reset ชื่อคลาส ฝังโค้ดของรายการคุณสมบัติที่สร้างไว้ก่อนหน้านี้ และตั้งค่า default ของคุณสมบัติทั้งหมดกลับไปที่ Reset เมธอดเสร็จสมบูรณ์

รหัสจะถูก contextSource.AddSource ส่งออกแยกกันสําหรับแต่ละคลาสในเมธอด อย่างไรก็ตาม เหตุผลที่ฉันใส่ "g" ในชื่อไฟล์คือเพื่อให้ง่ายต่อการตรวจสอบว่าเป็นโค้ดที่สร้างขึ้นด้วยตนเองหรือเกิดข้อผิดพลาดของโค้ดที่สร้างขึ้นโดยอัตโนมัติเมื่อมีข้อผิดพลาดในการสร้าง

หากคุณทําจริง คุณจะได้รับคําขอเช่น "ฉันต้องการรีเซ็ตฟิลด์", "ฉันต้องการเริ่มต้นนอกเหนือจากค่าเริ่มต้น", "ฉันต้องการรีเซ็ตเฉพาะคุณสมบัติอัตโนมัติ" ถ้าใส่เข้าไปสายไฟจะยาวเลยลองทําเองดูนะครับ

ด้านโครงการแอปพลิเคชัน

คราวนี้ฉันสร้างรหัสที่ขยายวิธีการของชั้นเรียนดังนั้นฉันจะสร้างชั้นเรียนอย่างเหมาะสม

เนื้อหามีดังนี้ แต่ถ้ามีคุณสมบัติสามารถใช้ส่วนที่เหลือได้ตามความเหมาะสม

Player.cs

public partial class Player
{
  public string Name { get; set; } = "";


  public float PositionX { get; set; }
  public float PositionY { get; set; }

  public int Level { get; set; } = 1;

  public override string ToString()
  {
    return $"{nameof(Player)} {{{nameof(Name)} = {Name}, {nameof(PositionX)} = {PositionX}, {nameof(PositionY)} = {PositionY}, {nameof(Level)} = {Level}}}";
  }
}

Item.cs

public partial class Item
{
  public string Name { get; set; } = "";

  public float PositionX { get; set; }
  public float PositionY { get; set; }

  public int Type { get; set; }

  public override string ToString()
  {
    return $"{nameof(Item)} {{{nameof(Name)} = {Name}, {nameof(PositionX)} = {PositionX}, {nameof(PositionY)} = {PositionY}, {nameof(Type)} = {Type}}}";
  }
}

เมธอดจะขยายเมื่อคุณ Reset สร้างโค้ด แต่อาจไม่ปรากฏใน Visual Studio ดังนั้นให้รีสตาร์ท Visual Studio ในเวลานั้น ฉันคิดว่าคุณจะเห็นว่ารหัสถูกขยายไปยังเครื่องวิเคราะห์โดยอัตโนมัติ

การเยื้องนั้นแปลก แต่คุณสามารถเพิกเฉยได้เพราะโดยพื้นฐานแล้วคุณจะไม่แตะต้องรหัสที่สร้างขึ้นโดยอัตโนมัติ

Program.csลองเขียนโค้ดเพื่อดูว่าResetคุณสามารถเรียกใช้วิธีการได้หรือไม่ ฉันคิดว่าผลลัพธ์ของการดําเนินการสะท้อนให้เห็นตามที่คาดไว้

Program.cs

Console.WriteLine("Hello, World!");

SampleClass.Hello();
Console.WriteLine();

Player player = new()
{
  Name = "Mike",
  PositionX = 10,
  PositionY = 5.5f,
  Level = 3,
};
Console.WriteLine(player);
player.Reset();
Console.WriteLine(player);
Console.WriteLine();

Item item = new()
{
  Name = "Banana",
  PositionX = 50,
  PositionY = 53.5f,
  Type = 12,
};
Console.WriteLine(item);
item.Reset();
Console.WriteLine(item);
Console.WriteLine();