C# 9.0で加わったC# Source Generatorと、それで作ったValueObjectGeneratorの紹介

# ValueObjectGenerator

この投稿では、C# 9.0で加わった`C# Source Generator`とそれを使って開発した`ValueObjectGenerator`を紹介します。

コードはこちら！

https://github.com/RyotaMurohoshi/ValueObjectGenerator


## 背景

次の`Product`クラスは、2つのint型のプロパティ`ProductId`と`ProductCategoryId`を持っています。

```csharp
public class Product
{
    public Product(string name, int productId, int productCategoryId)
    {
        Name = name;
        ProductId = productId;
        ProductCategoryId = productCategoryId;
    }

    public string Name { get; }
    public int ProductId { get; }
    public int ProductCategoryId { get; }
}
```

この型のインスタンスの利用シーンにおいて、いくつかの場所では`ProductId`が必要で、他の場所では`ProductCategoryId`が必要でしょう。どちらもint型で名前が似ているので、うっかりと`ProductId`と`ProductCategoryId`を取り違えてしまうかもしれません.

この取り違え型を防ぐにはどうしたらいいでしょうか？一つの方法としては、次のような`ProductId`型と`CategoryId`型を作り、それらを利用することです。これらの型を利用することで、コンパイラは`ProductId`プロパティと`ProductCategoryId`プロパティの取り違えを検出し、プログラム上のミスを防ぐことができます。このようにValueObjectクラス(もしくは、Wrapperクラス)を作成し利用することで、int型やstring型のプロパティの取り違えやミスを防ぐことができます。

`ProductId`型と`CategoryId`型は次のような感じになります。

```csharp
public sealed class ProductId: IEquatable<ProductId>
{
    public int Value { get; }

    public ProductId(int value)
    {
        Value = value;
    }

    public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is ProductId other && Equals(other);
    public override int GetHashCode() => Value.GetHashCode();
    public override string ToString() => Value.ToString();
    public static bool operator ==(ProductId left, ProductId right) => Equals(left, right);
    public static bool operator !=(ProductId left, ProductId right) => !Equals(left, right);

    public bool Equals(ProductId other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Value == other.Value;
    }

    public static explicit operator ProductId(int value) => new ProductId(value);
    public static explicit operator int(ProductId value) => value.Value;
}

public class CategroyId: IEquatable<CategroyId>
{
    public int Value { get; }

    public CategroyId(int value)
    {
        Value = value;
    }

    public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is CategroyId other && Equals(other);
    public override int GetHashCode() => Value.GetHashCode();
    public override string ToString() => Value.ToString();
    public static bool operator ==(CategroyId left, CategroyId right) => Equals(left, right);
    public static bool operator !=(CategroyId left, CategroyId right) => !Equals(left, right);

    public bool Equals(CategroyId other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Value == other.Value;
    }

    public static explicit operator CategroyId(int value) => new CategroyId(value);
    public static explicit operator int(CategroyId value) => value.Value;
}
```

この`ProductId`型と`CategoryId`型を使った、`Product`型はこんな感じ。

```csharp
public class Product
{
    public Product(string name, ProductId productId, CategroyId productCategoryId)
    {
        Name = name;
        ProductId = productId;
        ProductCategoryId = productCategoryId;
    }

    public string Name { get; }
    public ProductId ProductId { get; }
    public CategroyId ProductCategoryId { get; }
}
```

このようにValueObjectクラス(もしくは、Wrapperクラス)を作成し利用することでコンパイルエラーを防ぐことができます。よかった、よかった！けれど、これらのコードは非常に大量のボイラープレートで溢れていますね。長いですね！`ProductId`型と`CategoryId`型。長すぎます！これらのボイラープレートコードは、他の大切な意味のあるコードを読む際にとても邪魔です。

さて、こんなふうなボイラープレートコードには、C# 9.0から加わった`C# Source Generator`が大活躍します。

## C# Source Generatorはこんなの

`C# Source Generator`は、ビルド時にC#のソースコードを生成する仕組みです。

* メインのプロジェクトがビルドされる前にコード生成
* コード生成するために必要な入力値はコンパイル時に必要
* 出力結果は、プロジェクトの一部となる
* IDEにおいて、生成したコードの宣言にジャンプもできる
* ILではなくC#を生成するので、デバックがすごい楽
* 既存のソースコードを上書きしたりけしたりすることはできない


この`C# Source Generator`を使うことで、プログラマティカルにコード生成をすることができます。`C# Source Generator`をつかうことで、ボイラープレートのコードは非常に簡単になります。

他、参考。

* [Introducing C# Source Generators](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/)
* [New C# Source Generator Samples](https://devblogs.microsoft.com/dotnet/new-c-source-generator-samples/)
* [Source Generators Cookbook](https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.cookbook.md)
* [C# 9.0の新機能 コード ジェネレーターのサポート](https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-9#support-for-code-generators)

## 使い方

さてそんな`C# Source Generator`を使って、私が開発した、`ValueObjectGenerator`を紹介します。

次のように `IntValueObject` 属性をクラスに付与します。

```csharp
using ValueObjectGenerator;

[IntValueObject]
public partial class ProductId
{
}
```

そうすると次のようなコードが生成されます。

```csharp
public partial class ProductId: IEquatable<ProductId>
{
    public int Value { get; }

    public ProductId(int value)
    {
        Value = value;
    }

    public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is ProductId other && Equals(other);
    public override int GetHashCode() => Value.GetHashCode();
    public override string ToString() => Value.ToString();
    public static bool operator ==(ProductId left, ProductId right) => Equals(left, right);
    public static bool operator !=(ProductId left, ProductId right) => !Equals(left, right);

    public bool Equals(ProductId other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Value == other.Value;
    }

    public static explicit operator ProductId(int value) => new ProductId(value);
    public static explicit operator int(ProductId value) => value.Value;
}
```

`ProductId`は、ValueObjectクラス(もしくは、Wrapperクラス)です。

次のように、ValueObjectクラス(もしくは、Wrapperクラス)を定義するためにあった、大量のボイラープレートコードはなくなりました。

```csharp
[IntValueObject]
public class ProductId { }

[IntValueObject]
public class CategoryId { }

public class Product
{
    public Product(string name, ProductId productId, CategoryId productCategoryId)
    {
        Name = name;
        ProductId = productId;
        ProductCategoryId = productCategoryId;
    }

    public string Name { get; }
    public ProductId ProductId { get; }
    public CategoryId ProductCategoryId { get; }
}
```

こんなふうに、`ValueObjectGenerator`を使えば、 ValueObjectクラス(もしくは、Wrapperクラス)の生成に必要な大量のボイラープレートコードを排除することが可能です。

## ValueObjectGeneratorの機能

ValueObjectGeneratorの機能の紹介をします。

* サポートするValue型
* クラスと構造体のサポート
* プロパティの名前指定

### サポートするValue型

`ValueObjectGenerator`は、int型以外のValueObjectクラス(もしくは、Wrapperクラス)をサポートしています。

```csharp
    
[StringValueObject]
public partial class ProductName
{
}

public sealed partial class ProductName : System.IEquatable<ProductName>
{
    public string Value { get; }
    public ProductName(string value)
    {
        Value = value;
    }
    public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is ProductName other && Equals(other);
    public override int GetHashCode() => Value.GetHashCode();
    public override string ToString() => Value.ToString();
    public static bool operator ==(ProductName left, ProductName right) => Equals(left, right);
    public static bool operator !=(ProductName left, ProductName right) => !Equals(left, right);
    public bool Equals(ProductName other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Value == other.Value;
    }
    public static explicit operator ProductName(string value) => new ProductName(value);
    public static explicit operator string(ProductName value) => value.Value;
}
```

次のテープルは、提供している属性とそれに対応する型を示しています。

| 属性  | 型 |
----|----
| StringValueObject | string |
| IntValueObject | int |
| LongValueObject | long |
| FloatValueObject | float |
| DoubleValueObject | double |

### クラスと構造体のサポート

`ValueObjectGenerator`は、クラスと構造体、両方の生成のサポートをします。

まずは、クラス利用例。

```csharp
[IntValueObject]
public partial class IntClassSample
{
}
```

クラスの場合、次のようなコードが生成されます。

```csharp
public sealed partial class IntClassSample : System.IEquatable<IntClassSample>
{
    public int Value { get; }
    public IntClassSample(int value)
    {
        Value = value;
    }
    public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is IntClassSample other && Equals(other);
    public override int GetHashCode() => Value.GetHashCode();
    public override string ToString() => Value.ToString();
    public static bool operator ==(IntClassSample left, IntClassSample right) => Equals(left, right);
    public static bool operator !=(IntClassSample left, IntClassSample right) => !Equals(left, right);
    public bool Equals(IntClassSample other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Value == other.Value;
    }
    public static explicit operator IntClassSample(int value) => new IntClassSample(value);
    public static explicit operator int(IntClassSample value) => value.Value;
}
```

次に、構造体の利用例。


```csharp
[IntValueObject]
public partial struct IntStructSample
{
}
```

構造体の場合、次のようなコードが生成されます。

```csharp
public  partial struct IntStructSample : System.IEquatable<IntStructSample>
{
    public int Value { get; }
    public IntStructSample(int value)
    {
        Value = value;
    }
    public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is IntStructSample other && Equals(other);
    public override int GetHashCode() => Value.GetHashCode();
    public override string ToString() => Value.ToString();
    public static bool operator ==(IntStructSample left, IntStructSample right) => Equals(left, right);
    public static bool operator !=(IntStructSample left, IntStructSample right) => !Equals(left, right);
    public bool Equals(IntStructSample other)
    {
        
        
        return Value == other.Value;
    }
    public static explicit operator IntStructSample(int value) => new IntStructSample(value);
    public static explicit operator int(IntStructSample value) => value.Value;
}
```

### プロパティの名前指定

生成するValueObjectクラス(もしくは、Wrapperクラス)が持つ、プロパティの名前も指定することができます。

次のようにすることで、

```csharp
[StringValueObject(PropertyName = "StringValue")]
public partial class CustomizedPropertyName
{
}
```

次のような型が生成されます。

```csharp
public sealed partial class CustomizedPropertyName : System.IEquatable<CustomizedPropertyName>
{
    public string StringValue { get; }
    public CustomizedPropertyName(string value)
    {
        StringValue = value;
    }
    public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is CustomizedPropertyName other && Equals(other);
    public override int GetHashCode() => StringValue.GetHashCode();
    public override string ToString() => StringValue.ToString();
    public static bool operator ==(CustomizedPropertyName left, CustomizedPropertyName right) => Equals(left, right);
    public static bool operator !=(CustomizedPropertyName left, CustomizedPropertyName right) => !Equals(left, right);
    public bool Equals(CustomizedPropertyName other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return StringValue == other.StringValue;
    }
    public static explicit operator CustomizedPropertyName(string value) => new CustomizedPropertyName(value);
    public static explicit operator string(CustomizedPropertyName value) => value.StringValue;
}
```


## 今後の改善案

やりたいなと思っているのは、

* IComparableのサポート
* JSON serializer/deserializer
* 他のValueタイプ
* 算術演算のサポート

などです。


あと以前、[ufcpp](https://twitter.com/ufcpp)さんの[Youtube配信](https://youtu.be/QXT-TSvIbbY?t=4676)に、お邪魔した時に、[ufcpp](https://twitter.com/ufcpp)とゲストの[xin9le](https://twitter.com/xin9le)さんに、この`ValueObjectGenerator`を紹介した時に、いろいろご指摘をいただきました。その番組の指摘のおかげで

* string interpolationを使うとパフォーマンスが悪いので、パフォーマンスもよくする
* 余分なメンバを持っていたらコンパイルエラーにするアナライザー

も入れたほうがいいことがわかりました。[ufcpp](https://twitter.com/ufcpp)さん、[xin9le](https://twitter.com/xin9le)さん、ありがとうございます。

## もしかしたら、recordでいいかも？

先に紹介した、[ufcpp](https://twitter.com/ufcpp)さんの[Youtube配信](https://youtu.be/QXT-TSvIbbY?t=4676)番組で、「1要素Recordとそんなに変わらない」という指摘もいただきました。

```csharp
public record ProductId(int Value) { }
```

いや、もうその通りなんですよね。

ただ、Recordではできないものがあります。それは、「オーバーライドを認めないという」ものです。Recordではできないのでそれがメリットになります。と、[ufcpp](https://twitter.com/ufcpp)さんと[xin9le](https://twitter.com/xin9le)さんにご指摘いただきました！

ありがとうございます！

## まとめ

この投稿では、C# 9.0で加わった`C# Source Generator`とそれを使って開発した`ValueObjectGenerator`を紹介しました。

https://github.com/RyotaMurohoshi/ValueObjectGenerator

現在、ValueObjectGeneratorは開発・改善途中です。「便利じゃん！」「使いたい！」という方は、ぜひGitHubでStarをください！励ましになります！

ご意見、ご要望があれば、[GitHub](https://github.com/RyotaMurohoshi/ValueObjectGenerator)のissueか、Twitterの[@RyotaMurohoshi](https://twitter.com/RyotaMurohoshi)まで！