Sử dụng Visual Studio và Trình tạo Nguồn để tự động tạo mã

Trang Cập Nhật :
Ngày tạo trang :

Môi trường hoạt động

Visual Studio
  • Visual Studio 2022
.MẠNG
  • .NET 8.0

Điều kiện tiên quyết

Visual Studio
  • Nó hoạt động ngay cả với phiên bản cũ hơn một chút
.MẠNG
  • Nó hoạt động ngay cả với phiên bản cũ hơn một chút

Lúc đầu

Có một số kỹ thuật để tự động tạo mã với các định nghĩa của riêng bạn, nhưng trong bài viết này tôi sẽ chỉ cho bạn cách sử dụng Trình tạo nguồn. Một trong những lợi thế lớn nhất của trình tạo nguồn là nó phân tích cấu trúc mã nguồn của dự án hiện tại và tạo mã mới dựa trên nó. Ví dụ: khi bạn tạo một lớp mới, bạn có thể làm cho nó để mã được tự động thêm vào để khớp với lớp. Bạn có thể lập trình loại mã bạn muốn tạo, vì vậy bạn có thể tạo bất kỳ hình thức tạo mã tự động nào bạn thích.

Mã về cơ bản được tạo tự động trong nền và được tích hợp vào dự án đằng sau hậu trường. Vì nó không được xuất ra dưới dạng tệp theo cách hiển thị, nó không được sử dụng cho mục đích sử dụng lại mã được tạo tự động cho các mục đích chung (mặc dù nó có thể bị xóa bằng cách sao chép nó trong thời gian này). Tuy nhiên, vì mã được tạo tự động theo cấu trúc của dự án, nó làm giảm chi phí nhập liệu thủ công và giảm lỗi viết mã tương ứng, đây là một lợi thế rất lớn.

Trong bài viết này, tôi sẽ giải thích cách kiểm tra xem mã có được tạo tự động hay không, vì vậy tôi sẽ không đi xa đến mức thực sự phân tích sâu mã và thực hiện đầu ra nâng cao. Hãy tự tra cứu nó như một ứng dụng.

Thiết lập

Đầu tiên, cài đặt Visual Studio. Một lời giải thích ngắn gọn được tóm tắt trong các Lời khuyên sau đây.

Về cơ bản, bạn có thể sử dụng nó cho bất kỳ dự án nào, vì vậy không quan trọng bạn thiết lập khối lượng công việc nào. Tuy nhiên, lần này, với tư cách là một "thành phần riêng lẻ", ". SDK nền tảng trình biên dịch NET. Điều này rất hữu ích cho việc gỡ lỗi trong quá trình phát triển Trình tạo nguồn. Nếu bạn đã cài đặt Visual Studio, bạn có thể thêm nó từ menu Visual Studio trong Công cụ > Tải Công cụ và Tính năng.

Tạo và chuẩn bị một dự án máy phát điện nguồn

Trình tạo nguồn được tạo trong một dự án tách biệt với dự án ứng dụng chính. Không quan trọng nếu bạn tạo chúng trước hay tạo thêm sau. Trong trường hợp này, tôi sẽ tạo nó từ dự án Source Generator.

Trên màn hình Tạo dự án mới, chọn Thư viện lớp học.

Tên dự án có thể là bất cứ thứ gì, nhưng bây giờ CodeGenerator , chúng ta sẽ để nó là .

Đối với thư viện lớp, Source Generator hiện hỗ trợ . Tiêu chuẩn NET 2.0.

Khi bạn đã tạo dự án của mình, hãy tải gói với Microsoft.CodeAnalysis.CSharp NuGet. Hành vi có thể khác nhau tùy thuộc vào phiên bản, nhưng không có điểm nào trong việc tiếp tục sử dụng phiên bản cũ, vì vậy tôi sẽ đặt phiên bản mới nhất.

Sau đó mở tệp dự án dưới dạng mã.

Khi bạn mở nó, bạn sẽ thấy như sau.

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

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

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

</Project>

Thêm nó như sau: Hãy thêm bất cứ thứ gì khác bạn cần.

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

Tiếp theo, viết mã cho lớp sẽ tự động tạo mã. Trước hết, chúng tôi sẽ chỉ tạo một khung, vì vậy hãy viết lại mã hiện có từ đầu Class1.cs hoặc thêm mã mới.

Mã sẽ trông như thế này: Tên lớp có thể là bất cứ thứ gì, nhưng tốt hơn là nên có một tên hiển thị loại mã nào được tạo tự động. SampleGenerator Hiện tại, hãy để nó là . Initialize Trong phương pháp này, bạn sẽ viết quá trình phân tích mã và tạo mã.

using Microsoft.CodeAnalysis;

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

Tiếp theo, tạo một dự án cho ứng dụng mà bạn muốn tự động tạo mã. Nó có thể là bất cứ thứ gì, nhưng ở đây chúng tôi sẽ chỉ đơn giản sử dụng nó như một ứng dụng bảng điều khiển. Loại và phiên bản của khung của dự án mà mã được tạo tự động tương ứng với số chung.

Thêm tham chiếu đến dự án trình tạo nguồn từ dự án phía ứng dụng.

Một số cài đặt không thể được đặt trong thuộc tính, vì vậy hãy mở tệp dự án trong mã.

Tôi nghĩ nó trông như thế này:

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

Thêm cài đặt cho dự án được tham chiếu như sau: Đảm bảo rằng bạn không mắc lỗi cú pháp của 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>

Tiếp theo, mở thuộc tính dự án ở phía "Trình tạo mã".

Nhấp vào liên kết để mở giao diện người dùng thuộc tính khởi chạy gỡ lỗi.

Xóa hồ sơ gốc vì bạn không muốn sử dụng nó.

Thêm hồ sơ mới.

Chọn Thành phần Roslyn.

Nếu bạn đã thực hiện cài đặt cho đến nay, bạn sẽ có thể chọn dự án ứng dụng, vì vậy hãy chọn nó.

Đây là kết thúc của sự chuẩn bị.

Kiểm tra xem bạn có thể gỡ lỗi không

Mở mã nguồn và Initialize đặt điểm ngắt ở cuối phương thức.

Hãy gỡ lỗi trình tạo nguồn.

Nếu quá trình dừng tại điểm ngắt, bạn có thể xác nhận rằng bạn đang gỡ lỗi bình thường. Điều này sẽ làm cho việc phát triển trình tạo nguồn của bạn trở nên dễ dàng một cách hợp lý.

Hiện tại, hãy xuất một mã cố định

Đầu tiên, hãy thử xuất một mã cố định một cách dễ dàng. Thật dễ dàng vì bạn thậm chí không cần phải phân tích mã. Ngay cả khi nó là một mã cố định, nó được xử lý như một chuỗi, vì vậy có thể tăng sản xuất mã ở dạng cố định bằng cách xây dựng một chương trình.

Nếu bạn muốn xuất mã cố định, bạn context.RegisterPostInitializationOutput có thể làm như vậy bằng cách sử dụng . Sau đây là một ví dụ về đầu ra mã.

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

Nội dung của mã như được viết trong các bình luận, vì vậy tôi sẽ bỏ qua các chi tiết. Xây dựng nó và đảm bảo không có lỗi.

Khi bản dựng hoàn tất, bạn có thể mở rộng "Trình phân tích" trong dự án ứng dụng để xem mã được tạo bởi trình tạo mã.

Nếu bạn không nhìn thấy nó, khởi động lại Visual Studio và kiểm tra nó. Có vẻ như tại thời điểm này, Visual Studio có thể không được cập nhật trừ khi bạn khởi động lại nó. Thông minh và tô sáng cú pháp được sử dụng khi tạo chương trình là tương tự nhau. Tuy nhiên, vì bản thân mã được phản ánh tại thời điểm xây dựng, có vẻ như chương trình được phản ánh ở phía ứng dụng.

Sau khi mã được tạo, hãy thử sử dụng nó trong ứng dụng của bạn. Nó sẽ hoạt động tốt.

Phân tích và tạo mã

Nếu bạn chỉ xử lý mã bình thường và xuất ra, nó không khác nhiều so với việc tạo mã tự động khác và bạn không thể tận dụng lợi ích của trình tạo nguồn. Vì vậy, bây giờ chúng ta hãy phân tích code của dự án ứng dụng và tạo code cho phù hợp.

Tuy nhiên, việc phân tích code khá sâu, vì vậy tôi không thể giải thích mọi thứ ở đây. Hiện tại, tôi sẽ giải thích đến mức bạn có thể phân tích và xuất mã. Nếu bạn muốn đi sâu hơn, xin vui lòng làm nghiên cứu của riêng bạn.

Hiện tại, làm ví dụ, tôi muốn tự động tạo mã "thêm phương thức vào Reset tất cả các lớp đã tạo và đặt lại giá trị default của tất cả các thuộc tính thành ". Trong những năm gần đây, có vẻ như việc xử lý các phiên bản bất biến đã được ưu tiên hơn, nhưng trong các chương trình trò chơi, không thể tạo một phiên bản mới mỗi khung, vì vậy tôi nghĩ rằng có một cách sử dụng cho quá trình đặt lại giá trị. default Bạn có thể muốn thiết lập một cái gì đó khác hơn thế, nhưng nếu bạn làm điều đó, nó sẽ là một ví dụ đầu tiên dài, vì vậy hãy tự áp dụng nó.

Bên tạo mã

Tạo một lớp mới với mục đích giữ lại trình tạo mà bạn đã tạo trước đó. Nếu nội dung được tạo tự động thay đổi, tốt hơn là tạo một nội dung mới trong một lớp khác.

Trình biên dịch Roslyn đảm nhận việc cấu trúc mã, vì vậy những gì chúng ta sẽ làm ở đây là phân tích cú pháp dữ liệu có cấu trúc và viết mã.

Đầu tiên, tôi sẽ đăng tất cả các mã. Tôi đã cố gắng có càng ít mã càng tốt. Lần này nó hoạt động, nhưng nó chỉ ở cấp độ di chuyển như một Mr. / Ms., vì vậy khi bạn thực sự tiến hành phát triển, sẽ có khá nhiều phần còn thiếu. Vui lòng cải thiện ở đó khi cần thiết.

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

Tôi đã viết về nó trong các bình luận, nhưng tôi sẽ giải thích nó ở một số nơi.

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

context.SyntaxProvider.CreateSyntaxProvider để phân tích mã trong dự án ứng dụng và cấu trúc nó càng nhiều càng tốt. Mọi thứ được chia thành các nút và chúng tôi sử dụng để xác định nút nào chúng tôi predicate muốn xử lý. Ngoài ra, nếu cần transform , chuyển đổi đối tượng đã xử lý với và chuyển nó sang phía đầu ra.

predicate Trong trường hợp này, chúng tôi đang làm như sau.

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

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

Trước hết, như với bất kỳ quy trình nào, quá trình tự động tạo mã luôn có thể bị hủy sớm. Vì vậy, hãy sử dụng mã thông báo hủy để ThrowIfCancellationRequested gọi để bạn có thể ngắt lời bất cứ lúc nào.

predicate Bây giờ mọi nút được gọi, chúng tôi muốn xác định nút nào là nút chúng tôi muốn xử lý. Vì có một số lượng lớn trong số họ, tốt hơn là thu hẹp chúng ở đây ở một mức độ nào đó.

Vì chúng ta sẽ thêm processing vào class lần này, chúng ta ClassDeclarationSyntax sẽ xác định xem nó có phải là hay không và xác định xem chỉ có class sẽ được xử lý hay không. partial class Ngoài ra, vì mã được đính kèm với , nó partial class được đặt như một phán quyết.

// 対象のノードをコード出力に必要な形に変換します
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 Cho phép bạn chuyển đổi phân tích thành dạng yêu cầu và chuyển nó đến đầu ra mã. Lần này, chúng tôi không thực hiện chuyển đổi nhiều, nhưng chuyển giá trị sang phía đầu ra như nó là return . Chúng tôi đang nhận được "các biểu tượng được khai báo" trên đường đi, nhưngSemanticModel chúng tôi có thể nhận được rất nhiều thông tin bổ sung bằng cách sử dụng và , Syntac vì vậy tôi nghĩ chúng tôi có thể lấy nó nếu cần thiết. return Tuples được tạo và trả về, nhưng cấu hình của dữ liệu được chuyển đến phía đầu ra có thể là bất cứ điều gì. Nếu bạn muốn truyền nhiều dữ liệu, bạn có thể tạo một bộ dữ liệu như thế này hoặc bạn có thể xác định lớp của riêng mình và truyền nó vào.

// 解析し変換した情報をもとにコードを出力します
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 Bây giờ, chúng ta sẽ tạo và xuất code dựa trên dữ liệu sẽ được xử lý bởi . context.SyntaxProvider.CreateSyntaxProvider return Giá trị của có thể được nhận dưới dạng đối số thứ hai của Action , vì vậy mã được tạo dựa trên giá trị đó.

Trong trường hợp này, chúng ta đang tạo code để lấy danh sách cú pháp của các thuộc tính từ cú pháp của class và thiết lập default thuộc tính từ mỗi name.

Sau đó, tạo một phương thức dựa trên Reset tên lớp, nhúng mã của danh sách thuộc tính đã tạo trước đó và đặt giá trị default của tất cả các thuộc tính trở lại Reset Phương thức đã hoàn thành.

Mã được contextSource.AddSource xuất riêng cho từng lớp trong phương thức. Nhân tiện, lý do tại sao tôi đặt "g" trong tên tệp là để dễ dàng xác định xem đó là mã được tạo thủ công hay lỗi mã được tạo tự động khi có lỗi xây dựng.

Nếu bạn thực sự thực hiện nó, bạn sẽ nhận được các yêu cầu như "Tôi muốn đặt lại trường", "Tôi muốn khởi tạo nó khác với mặc định", "Tôi chỉ muốn đặt lại các thuộc tính tự động". Nếu bạn đặt chúng vào, dây sẽ dài, vì vậy hãy cố gắng tự làm.

Mặt dự án ứng dụng

Lần này, mình tạo code mở rộng phương thức của class nên sẽ tạo class một cách thích hợp.

Nội dung như sau, nhưng nếu có thuộc tính, bạn có thể sử dụng phần còn lại cho phù hợp.

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

Phương pháp được mở rộng khi Reset bạn tạo mã, nhưng nó có thể không được phản ánh trong Visual Studio, vì vậy khởi động lại Visual Studio tại thời điểm đó. Tôi nghĩ bạn có thể thấy rằng mã được tự động mở rộng cho trình phân tích.

Thụt lề là lạ, nhưng bạn có thể bỏ qua nó vì về cơ bản bạn không chạm vào mã được tạo tự động.

Program.cs Hãy thử viết mã vào để xem Reset bạn có thể gọi phương thức này không. Tôi nghĩ rằng kết quả của việc thực hiện được phản ánh như mong đợi.

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