استخدم Visual Studio ومولد المصدر لإنشاء التعليمات البرمجية تلقائيا

تحديث الصفحة :
تاريخ إنشاء الصفحة :

بيئة التشغيل

فيجوال ستوديو
  • فيجوال ستوديو 2022
.صافي
  • .NET 8.0

المتطلبات المسبقه

فيجوال ستوديو
  • يعمل حتى مع إصدار أقدم إلى حد ما
.صافي
  • يعمل حتى مع إصدار أقدم إلى حد ما

في البداية

هناك العديد من التقنيات لإنشاء التعليمات البرمجية تلقائيا باستخدام تعريفاتك الخاصة ، ولكن في هذه المقالة سأوضح لك كيفية استخدام Source Generator. واحدة من أكبر مزايا مولد المصدر هي أنه يحلل بنية الكود المصدري للمشروع الحالي ويولد رمزا جديدا بناء عليه. على سبيل المثال ، عند إنشاء فصل دراسي جديد ، يمكنك جعله بحيث تتم إضافة الرمز تلقائيا لمطابقة الفئة. يمكنك برمجة نوع التعليمات البرمجية التي تريد إنشاؤها ، بحيث يمكنك إنشاء أي شكل من أشكال إنشاء التعليمات البرمجية التلقائي الذي تريده.

يتم إنشاء الكود تلقائيا بشكل أساسي في الخلفية ويتم دمجه في المشروع خلف الكواليس. نظرا لأنه لا يتم إخراجه كملف بطريقة مرئية ، فلا يتم استخدامه لغرض إعادة استخدام الكود الذي تم إنشاؤه تلقائيا للأغراض العامة (على الرغم من أنه يمكن إزالته عن طريق نسخه في الوقت الحالي). ومع ذلك ، نظرا لأن الكود يتم إنشاؤه تلقائيا وفقا لهيكل المشروع ، فإنه يقلل من تكلفة الإدخال اليدوي ويقلل من أخطاء كتابة التعليمات البرمجية وفقا لذلك ، وهي ميزة كبيرة.

في هذه المقالة ، سأشرح كيفية التحقق من إنشاء الكود تلقائيا ، لذلك لن أذهب إلى حد تحليل الكود بعمق وإجراء مخرجات متقدمة. يرجى البحث عنه بنفسك كتطبيق.

اعداد

أولا ، قم بتثبيت Visual Studio. يتم تلخيص شرح موجز في النصائح التالية.

في الأساس ، يمكنك استخدامه لأي مشروع ، لذلك لا يهم عبء العمل الذي قمت بإعداده. ومع ذلك ، هذه المرة ، باعتبارها "مكونا فرديا" ، ". NET Compiler Platform SDK. هذا مفيد لتصحيح الأخطاء أثناء تطوير مولد المصدر. إذا كان لديك بالفعل Visual Studio مثبتا، يمكنك إضافته من القائمة Visual Studio ضمن أدوات > الحصول على أدوات وميزات.

إنشاء وإعداد مشروع مولد المصدر

يتم إنشاء مولد المصدر في مشروع منفصل عن مشروع التطبيق الرئيسي. لا يهم إذا قمت بإنشائها أولا أو إنشاء أخرى إضافية لاحقا. في هذه الحالة ، سأقوم بإنشائه من مشروع مولد المصدر.

في شاشة إنشاء مشروع جديد، حدد مكتبة الصفوف.

يمكن أن يكون اسم المشروع أي شيء ، ولكن في الوقت الحالي 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>

بعد ذلك ، افتح خصائص المشروع على "جانب مولد الكود".

انقر فوق الارتباط لفتح واجهة مستخدم خصائص تشغيل التصحيح.

احذف ملف التعريف الأصلي لأنك لا تريد استخدامه.

إضافة ملف تعريف جديد.

حدد مكون روزلين.

إذا قمت بإجراء الإعدادات حتى الآن ، فيجب أن تكون قادرا على تحديد مشروع التطبيق ، لذا حدده.

هذه هي نهاية التحضير.

تحقق مما إذا كان يمكنك تصحيح الأخطاء

افتح رمز المولد المصدر وضع 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 إلا إذا قمت بإعادة تشغيله. الذكاء وتمييز بناء الجملة المستخدمة عند إنشاء البرامج متشابهة. ومع ذلك ، نظرا لأن الكود نفسه ينعكس في وقت الإنشاء ، يبدو أن البرنامج ينعكس على جانب التطبيق.

بمجرد إنشاء الرمز ، حاول استخدامه في التطبيق الخاص بك. يجب أن تعمل بشكل جيد.

تحليل وإنشاء التعليمات البرمجية

إذا قمت فقط بمعالجة الكود بشكل طبيعي وإخراجه ، فإنه لا يختلف كثيرا عن إنشاء التعليمات البرمجية التلقائي الآخر ، ولا يمكنك الاستفادة من مزايا مولد المصدر. لذلك دعونا الآن نحلل رمز مشروع التطبيق وننشئ الكود وفقا لذلك.

ومع ذلك ، فإن تحليل الكود عميق جدا ، لذلك لا يمكنني شرح كل شيء هنا. في الوقت الحالي ، سأشرح حتى النقطة التي يمكنك فيها تحليل الكود وإخراجه. إذا كنت تريد التعمق أكثر ، فيرجى إجراء البحث الخاص بك.

في الوقت الحالي ، على سبيل المثال ، أود إنشاء الكود تلقائيا "إضافة طريقة إلى Reset جميع الفئات التي تم إنشاؤها وإعادة تعيين قيمة default جميع الخصائص إلى ". في السنوات الأخيرة ، يبدو أن التعامل مع الحالات غير القابلة للتغيير كان مفضلا ، ولكن في برامج الألعاب ، لا يمكن إنشاء مثيل جديد في كل إطار ، لذلك أعتقد أن هناك استخداما لعملية إعادة تعيين القيمة. default قد ترغب في تعيين شيء آخر غير ذلك ، ولكن إذا قمت بذلك ، فسيكون مثالا أوليا طويلا ، لذا يرجى تطبيقه بنفسك.

جانب مولد الكود

قم بإنشاء فئة جديدة لغرض الاحتفاظ بالمولد الذي قمت بإنشائه من قبل. إذا تغير المحتوى المراد إنشاؤه تلقائيا ، فمن الأفضل إنشاء محتوى جديد في فصل آخر.

يعتني مترجم Roslyn بهيكلة الكود ، لذا فإن ما سنفعله هنا هو تحليل البيانات المنظمة وكتابة الكود.

أولا ، سأقوم بنشر كل الكود. لقد حاولت الحصول على أقل قدر ممكن من التعليمات البرمجية. هذه المرة تعمل ، لكنها فقط على مستوى يتحرك كسيد / سيدة ، لذلك عندما تمضي قدما في التطوير فعليا ، سيكون هناك عدد غير قليل من الأجزاء المفقودة. يرجى تحسين هناك حسب الحاجة.

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();