既存のアセンブリのシンプルなクラスをRoslynを使用してコードに戻す方法

既存のプログラムアセンブリからエンティティコードのコピーを作成する必要がある場合がありますが、これらのコードを手動でコピーするのは時間がかかる場合があります。このプロセスを簡素化するために、`Roslyn`を使用してコードを自動生成できます。

この記事では、`Roslyn`を使用してシンプルなクラスをコードに戻す方法について説明します。

## 実現方法

この機能を実現するには、次の手順が必要です。

1. クラス内のすべてのプロパティ型がサポートされている型リストに含まれているかをチェックします。

2. C＃コード生成ユニットを作成し、必要な名前空間を追加します。

3. 名前空間を作成し、クラス定義を追加します。

4. クラスのすべてのプロパティをクラス定義に追加します。

5. 各プロパティに`get/set`メソッドを追加し、`default`値を設定します。

## コードの実装

### サポートされる型をチェックします

`PropertyInfo`配列から、サポートされるプロパティ型かどうかをチェックする静的メソッドです。まず、サポートされるプロパティ型のリストを定義し、その中に`GUID`、`DateTime`、`string`、`int`などが含まれます。

次に、プロパティ情報の配列を順番にチェックして、各プロパティの型が配列の場合は、配列の要素型を取得します。その後、サポートされるプロパティ型のリストに含まれている場合は、次のプロパティに進みます。サポートされていないプロパティ型が見つかった場合は、`NotSupportedException`をスローします。

```c#
private static void CheckSamplePropertiesTypes(PropertyInfo[] propertyInfos)
{
    var supportTypes = new[]
    {
        typeof(Guid),
        typeof(DateTime),
        typeof(string),

        typeof(int),
        typeof(char),
        typeof(uint),
        typeof(short),
        typeof(ushort),
        typeof(long),
        typeof(ulong),
        typeof(bool),
        typeof(float),
        typeof(double),
        typeof(byte)
    };

    foreach (var p in propertyInfos)
    {
        var propertyType = p.PropertyType;
        if (propertyType.BaseType == typeof(Array))
        {
            propertyType = propertyType.GetElementType();
        }

        if (supportTypes.Contains(propertyType))
        {
            continue;
        }

        throw new NotSupportedException();
    }
}
```

### プロパティを追加します

プロパティの名前と型から、プロパティ定義を作成します。その後、プロパティの`get/set`アクセサを設定し、`AddPropertyInitValue`メソッドを使用してプロパティの初期値を設定します。最後に、`AddMembers`メソッドを使用して、クラス定義に新しいプロパティを追加します。

`AddPropertyInitValue`メソッドは、プロパティの初期値を設定するために使用されます。まず、プロパティの型に応じて、初期値を設定するためのコードを生成します。例えば、プロパティの型が`string`の場合は、初期値に空文字列を設定します。また、プロパティの型が配列の場合は、要素の型から配列を生成し、初期値に設定します。

```c#
private static ClassDeclarationSyntax AddProperty(ClassDeclarationSyntax classDeclaration, PropertyInfo propertyInfo)
{
    var propertyDeclaration = SyntaxFactory.PropertyDeclaration(
            SyntaxFactory.ParseTypeName(propertyInfo.PropertyType.Name),
            propertyInfo.Name)
        .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
        .AddAccessorListAccessors(
            SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
                .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
            SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
                .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)));


    propertyDeclaration = AddPropertyInitValue(propertyDeclaration, propertyInfo);

    return classDeclaration.AddMembers(propertyDeclaration);
}

private static PropertyDeclarationSyntax AddPropertyInitValue(
    PropertyDeclarationSyntax propertyDeclaration,
    PropertyInfo propertyInfo)
{
    var propertyType = propertyInfo.PropertyType;
    var newLine = Environment.NewLine;

    if (propertyType == typeof(string))
    {
        propertyDeclaration = propertyDeclaration.WithInitializer(
                SyntaxFactory.EqualsValueClause(
                    SyntaxFactory.LiteralExpression(
                        SyntaxKind.StringLiteralExpression,
                        SyntaxFactory.Literal(""))))
            .WithSemicolonToken(
                SyntaxFactory.Token(SyntaxKind.SemicolonToken))
            .WithTrailingTrivia(SyntaxFactory.Comment(newLine));
    }
    else if (propertyType.BaseType == typeof(Array))
    {
        var elementType = propertyType.GetElementType()!;

        propertyDeclaration = propertyDeclaration.WithInitializer(
                SyntaxFactory.EqualsValueClause(
                    SyntaxFactory.ArrayCreationExpression(
                        SyntaxFactory
                            .ArrayType(SyntaxFactory.ParseTypeName(elementType.Name))
                            .WithRankSpecifiers(
                                SyntaxFactory.SingletonList(
                                    SyntaxFactory.ArrayRankSpecifier(
                                        SyntaxFactory.SingletonSeparatedList<ExpressionSyntax>(
                                            SyntaxFactory.LiteralExpression(
                                                SyntaxKind.NumericLiteralExpression,
                                                SyntaxFactory.Literal(0)))))))))
            .WithSemicolonToken(
                SyntaxFactory.Token(SyntaxKind.SemicolonToken))
            .WithTrailingTrivia(SyntaxFactory.Comment(newLine));
    }

    return propertyDeclaration;
}
```

### クラスと名前空間を追加して組み立てます

この関数は、与えられた`Type`型のクラスの全てのプロパティが支援される型であることを確認し、C#構文ツリーを生成して返します。生成されたC#コードは`string`として返されます。

```c#
public static string GenerateSampleClass(
    Type type,
    string newClassNamespace,
    Func<Type, PropertyInfo[]>? getPropertyInfosFunc = null)
{
    getPropertyInfosFunc ??= t => t.GetProperties();

    var allProperties = getPropertyInfosFunc.Invoke(type);
    CheckSamplePropertiesTypes(allProperties);

    var syntax = SyntaxFactory.CompilationUnit()
        .AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System")))
        .WithLeadingTrivia(SyntaxFactory.Comment("// ReSharper disable All"));

    var @namespace = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(newClassNamespace))
        .NormalizeWhitespace();
    var classDeclaration = SyntaxFactory.ClassDeclaration(type.Name)
        .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword));


    foreach (var p in allProperties)
    {
        classDeclaration = AddProperty(classDeclaration, p);
    }

    @namespace = @namespace.AddMembers(classDeclaration);
    syntax = syntax.AddMembers(@namespace);

    return syntax.NormalizeWhitespace().ToFullString();
}
```

## 全部のコード

- CodeGenerateTools.cs

```c#
public static class CodeGenerateTools
{
    private static void CheckSamplePropertiesTypes(PropertyInfo[] propertyInfos)
    {
        var supportTypes = new[]
        {
            typeof(Guid),
            typeof(DateTime),
            typeof(string),

            typeof(int),
            typeof(char),
            typeof(uint),
            typeof(short),
            typeof(ushort),
            typeof(long),
            typeof(ulong),
            typeof(bool),
            typeof(float),
            typeof(double),
            typeof(byte)
        };

        foreach (var p in propertyInfos)
        {
            var propertyType = p.PropertyType;
            if (propertyType.BaseType == typeof(Array))
            {
                propertyType = propertyType.GetElementType();
            }

            if (supportTypes.Contains(propertyType))
            {
                continue;
            }

            throw new NotSupportedException();
        }
    }

    public static string GenerateSampleClass(
        Type type,
        string newClassNamespace,
        Func<Type, PropertyInfo[]>? getPropertyInfosFunc = null)
    {
        getPropertyInfosFunc ??= t => t.GetProperties();

        var allProperties = getPropertyInfosFunc.Invoke(type);
        CheckSamplePropertiesTypes(allProperties);

        var syntax = SyntaxFactory.CompilationUnit()
            .AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System")))
            .WithLeadingTrivia(SyntaxFactory.Comment("// ReSharper disable All"));

        var @namespace = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(newClassNamespace))
            .NormalizeWhitespace();
        var classDeclaration = SyntaxFactory.ClassDeclaration(type.Name)
            .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword));


        foreach (var p in allProperties)
        {
            classDeclaration = AddProperty(classDeclaration, p);
        }

        @namespace = @namespace.AddMembers(classDeclaration);
        syntax = syntax.AddMembers(@namespace);

        return syntax.NormalizeWhitespace().ToFullString();
    }

    private static ClassDeclarationSyntax AddProperty(ClassDeclarationSyntax classDeclaration, PropertyInfo propertyInfo)
    {
        var propertyDeclaration = SyntaxFactory.PropertyDeclaration(
                SyntaxFactory.ParseTypeName(propertyInfo.PropertyType.Name),
                propertyInfo.Name)
            .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
            .AddAccessorListAccessors(
                SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
                    .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
                SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
                    .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)));


        propertyDeclaration = AddPropertyInitValue(propertyDeclaration, propertyInfo);

        return classDeclaration.AddMembers(propertyDeclaration);
    }


    private static PropertyDeclarationSyntax AddPropertyInitValue(
        PropertyDeclarationSyntax propertyDeclaration,
        PropertyInfo propertyInfo)
    {
        var propertyType = propertyInfo.PropertyType;
        var newLine = Environment.NewLine;

        if (propertyType == typeof(string))
        {
            propertyDeclaration = propertyDeclaration.WithInitializer(
                    SyntaxFactory.EqualsValueClause(
                        SyntaxFactory.LiteralExpression(
                            SyntaxKind.StringLiteralExpression,
                            SyntaxFactory.Literal(""))))
                .WithSemicolonToken(
                    SyntaxFactory.Token(SyntaxKind.SemicolonToken))
                .WithTrailingTrivia(SyntaxFactory.Comment(newLine));
        }
        else if (propertyType.BaseType == typeof(Array))
        {
            var elementType = propertyType.GetElementType()!;

            propertyDeclaration = propertyDeclaration.WithInitializer(
                    SyntaxFactory.EqualsValueClause(
                        SyntaxFactory.ArrayCreationExpression(
                            SyntaxFactory
                                .ArrayType(SyntaxFactory.ParseTypeName(elementType.Name))
                                .WithRankSpecifiers(
                                    SyntaxFactory.SingletonList(
                                        SyntaxFactory.ArrayRankSpecifier(
                                            SyntaxFactory.SingletonSeparatedList<ExpressionSyntax>(
                                                SyntaxFactory.LiteralExpression(
                                                    SyntaxKind.NumericLiteralExpression,
                                                    SyntaxFactory.Literal(0)))))))))
                .WithSemicolonToken(
                    SyntaxFactory.Token(SyntaxKind.SemicolonToken))
                .WithTrailingTrivia(SyntaxFactory.Comment(newLine));
        }

        return propertyDeclaration;
    }
}
```