Roslyn API でフィールドとプロパティの型と名称を取得する

# はじめに

この記事は株式会社ラグザイア [Advent Calendar 2023](https://qiita.com/advent-calendar/2023/luxiar)の記事です。

[前回](https://qiita.com/lx-sasabo/items/d3791b305d85bb686ae4)は Roslyn API のざっくりした概要の紹介とフィールドの名称を取得してみました。
今回はRoslyn API でフィールドとプロパティの型と名称を取得してみます。

先に忙しい方向けに、Roslyn API でフィールドとプロパティの型と名称を取得するコードを貼っておきます。

```program.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace SyntaxQuickStart
{
    class Program
    {
        const string programText =
@"namespace DummyNamespace
{
    internal class Dummy
    {
        public string DummyField = ""フィールドです"";
        public int DummyProperty => 123;
    }
}";

        static void Main()
        {
            SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
            CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

            // フィールドを取得
            FieldDeclarationSyntax field = root.DescendantNodes().OfType<FieldDeclarationSyntax>().First();

            Console.WriteLine("------------------ フィールドの情報 ------------------");
            // フィールドの名称を表示
            Console.WriteLine(field.Declaration.Variables.First().Identifier.ValueText);
            // フィールドの型を表示
            Console.WriteLine(field.Declaration.Type.ToString());

            // プロパティを取得
            PropertyDeclarationSyntax property = root.DescendantNodes().OfType<PropertyDeclarationSyntax>().First();

            Console.WriteLine("------------------ プロパティの情報 ------------------");
            // プロパティの名称を表示
            Console.WriteLine(property.Identifier.ValueText);
            // プロパティの型を表示
            Console.WriteLine(property.Type.ToString());
        }
    }
}
```

# フィールドの型を取得する

前回は Roslyn API を使ってフィールドの名称を表示するところまで行いました。同様に Roslyn API を使ってフィールドの型を表示してみましょう。

フィールドの名称の場合、コード解析後のツリーから `FieldDeclarationSyntax` を `OfType` を使って絞り込み、 `field.Declaration.Variables.First().Identifier.ValueText` のような形で名称を取りだしていました。コードはこんな感じでした。

```cs
var tree = CSharpSyntaxTree.ParseText(programText);
var root = tree.GetCompilationUnitRoot();

// フィールドを取得
var field = root.DescendantNodes().OfType<FieldDeclarationSyntax>().First();

// フィールドの名称を標準出力に表示
Console.WriteLine(field.Declaration.Variables.First().Identifier.ValueText);
```

`Declaration` プロパティがフィールドに関するあれこれの情報をもっていそうです。 `Declaration` の正体である [VariableDeclarationSyntax](https://learn.microsoft.com/ja-jp/dotnet/api/microsoft.codeanalysis.csharp.syntax.variabledeclarationsyntax?view=roslyn-dotnet-4.7.0) を見ると `Type` プロパティを発見しました。これが使えそうです。

```cs
// フィールドを取得
FieldDeclarationSyntax field = root.DescendantNodes().OfType<FieldDeclarationSyntax>().First();
// フィールドの型を表示
Console.WriteLine(field.Declaration.Type.ToString());
```

これを前回のコードに追加して表示してみます。

```program.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace SyntaxQuickStart
{
    class Program
    {
        const string programText =
@"namespace DummyNamespace
{
    internal class Dummy
    {
        public string DummyField = ""フィールドです"";
    }
}";

        static void Main()
        {
            SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
            CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

            // フィールドを取得
            FieldDeclarationSyntax field = root.DescendantNodes().OfType<FieldDeclarationSyntax>().First();

            Console.WriteLine("------------------ フィールドの情報 ------------------");
            // フィールドの名称を表示
            Console.WriteLine(field.Declaration.Variables.First().Identifier.ValueText);
            // フィールドの型を表示
            Console.WriteLine(field.Declaration.Type.ToString());
        }
    }
}
```

実行結果です。無事にフィールドの型である `string` が表示できました。
![Monosnap ~_works_SyntaxQuickStart 2023-11-27 16.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/8a64f8a5-606e-9942-0859-4e57bddec87a.png)

# プロパティの情報を取得する

フィールドの名称と型の情報は取得できました。同様にプロパティの情報も取得していきましょう。

## プロパティの名称を取得する

フィールドの場合↓のような形で名称 = 識別子を取得できていました。

```cs
var tree = CSharpSyntaxTree.ParseText(programText);
var root = tree.GetCompilationUnitRoot();

// フィールドを取得
var field = root.DescendantNodes().OfType<FieldDeclarationSyntax>().First();

// フィールドの名称を標準出力に表示
Console.WriteLine(field.Declaration.Variables.First().Identifier.ValueText);
```

↑でいけるのであれば `FieldDeclarationSyntax` の部分を `PropertyDeclarationSyntax` に変えるだけでいけるのでは？と予想してみました。やってみましょう。こうです！えいや！

```cs
// フィールドを取得して名称を表示（ネタバレ：エラーになります）
PropertyDeclarationSyntax property = root.DescendantNodes().OfType<PropertyDeclarationSyntax>().First();
Console.WriteLine(property.Declaration.Variables.First().Identifier.ValueText);
```

すると残念なことにエラーになりました。`PropertyDeclarationSyntax` は `Declaration` というプロパティは持たないようです。ちくしょう…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/ad56e4ab-ae78-6b23-4eb0-53d70c618674.png)

[PropertyDeclarationSyntax](https://learn.microsoft.com/ja-jp/dotnet/api/microsoft.codeanalysis.csharp.syntax.propertydeclarationsyntax?view=roslyn-dotnet-4.7.0) の解説ページを見ると `Identifier` プロパティがありました。
`property.Identifer` で一気に識別子（名称）の値が取れるみたいです。つまり、こうです。

```cs
PropertyDeclarationSyntax property = root.DescendantNodes().OfType<PropertyDeclarationSyntax>().First();
Console.WriteLine(property.Identifier.ValueText);
```

前回のコードに加えて出力させてみましょう。
```program.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace SyntaxQuickStart
{
    class Program
    {
        const string programText =
@"namespace DummyNamespace
{
    internal class Dummy
    {
        public string DummyField = ""フィールドです"";
        public int DummyProperty => 123;
    }
}";

        static void Main()
        {
            SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
            CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

            // フィールドを取得
            FieldDeclarationSyntax field = root.DescendantNodes().OfType<FieldDeclarationSyntax>().First();

            Console.WriteLine("------------------ フィールドの情報 ------------------");
            // フィールドの名称を表示
            Console.WriteLine(field.Declaration.Variables.First().Identifier.ValueText);
            // フィールドの型を表示
            Console.WriteLine(field.Declaration.Type.ToString());

            // プロパティを取得
            PropertyDeclarationSyntax property = root.DescendantNodes().OfType<PropertyDeclarationSyntax>().First();

            Console.WriteLine("------------------ プロパティの情報 ------------------");
            // プロパティの名称を表示
            Console.WriteLine(property.Identifier.ValueText);
        }
    }
}

```

実行結果です。プロパティの名称である `DummyProperty` が表示されました。いえーい。
![Monosnap ~_works_SyntaxQuickStart 2023-11-27 16.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/a012fa5f-8345-81b5-474e-7a03a1a33906.png)

## プロパティの型を取得する

プロパティの名称と同じように[PropertyDeclarationSyntax](https://learn.microsoft.com/ja-jp/dotnet/api/microsoft.codeanalysis.csharp.syntax.propertydeclarationsyntax?view=roslyn-dotnet-4.7.0) から何かできないかと見てみると、 `Type` プロパティがありました。これは勝ち確定パターンです。

こう書けそうです。
```cs
// プロパティを取得
PropertyDeclarationSyntax property = root.DescendantNodes().OfType<PropertyDeclarationSyntax>().First();
// プロパティの型を表示
Console.WriteLine(property.Type.ToString());
```

今までのコードに追加してみます。
```program.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace SyntaxQuickStart
{
    class Program
    {
        const string programText =
@"namespace DummyNamespace
{
    internal class Dummy
    {
        public string DummyField = ""フィールドです"";
        public int DummyProperty => 123;
    }
}";

        static void Main()
        {
            SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
            CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

            // フィールドを取得
            FieldDeclarationSyntax field = root.DescendantNodes().OfType<FieldDeclarationSyntax>().First();

            Console.WriteLine("------------------ フィールドの情報 ------------------");
            // フィールドの名称を表示
            Console.WriteLine(field.Declaration.Variables.First().Identifier.ValueText);
            // フィールドの型を表示
            Console.WriteLine(field.Declaration.Type.ToString());

            // プロパティを取得
            PropertyDeclarationSyntax property = root.DescendantNodes().OfType<PropertyDeclarationSyntax>().First();

            Console.WriteLine("------------------ プロパティの情報 ------------------");
            // プロパティの名称を表示
            Console.WriteLine(property.Identifier.ValueText);
            // プロパティの型を表示
            Console.WriteLine(property.Type.ToString());
        }
    }
}
```

実行結果です。プロパティの型の `int` が出力できました！
![Monosnap ~_works_SyntaxQuickStart 2023-11-27 16.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/0e52e8ca-6684-51b7-7926-8f3adcd44a37.png)

# 次回予告

ということで Roslyn API を使ってフィールドとプロパティの型と名称を取得することができました。次回は Roslyn API を使おうと思ったきっかけの「コメント」の取得に挑みます。乞うご期待。