Source GeneratorでNullObjectを自動生成する

この記事は[Unity Advent Calendar 2023](https://qiita.com/advent-calendar/2023/unity)　19日目の記事です。
昨日は[【AudioMixer】exposed parametersの操作クラスを自動生成する](https://qiita.com/amenone_games/items/8fcd2e2703c26bb8b4b8)でした。
明日は@Teach　さんの　[Unity と toio と M5AtomS3 を Bluetoothで繋げた話](https://qiita.com/Teach/items/47b03bc422a2e4a92820)　です！

また、同じくSourceGenerator関係の記事として[Source Generatorでプロパティを自動実装してみた](https://qiita.com/amenone_games/items/25c857608af0138eb646)も投稿しているので、よろしければご覧ください！

# NullObjectとは
NullObjectは **Nullチェックを減らすためのデザインパターン** です。

提供側がNullのかわりに、何もしないオブジェクトを渡すことで、
利用者側がNullエラーのことを考えずに使えるようにしよう、という考え方です！

これで憎き *NullException* に *Say Good-bye!!!*

色々な解説サイトや本に詳しい説明があるため、詳細は割愛しますが、
FactoryパターンやDIコンテナと組み合わせると、地味ながら縁の下の力持ち的な存在になります！

::: note
私は[Adaptive Code](https://www.amazon.co.jp/Adaptive-Code-%E5%AE%9F%E8%B7%B5%E9%96%8B%E7%99%BA%E6%89%8B%E6%B3%95-%E7%AC%AC2%E7%89%88-%E3%83%9E%E3%82%A4%E3%82%AF%E3%83%AD%E3%82%BD%E3%83%95%E3%83%88%E9%96%A2%E9%80%A3%E6%9B%B8/dp/4822253546)で知りました。
interfaceと併せた便利な使い方も詳述されており、オススメです！
:::

# NullObjectの欠点
とても良い考え方なのですが、
**作るのがめんどくさい！**

そう、**作るのがめんどくさい**のです！

# 自動生成しよう！
そういうわけで、自動生成機能を作りました！

https://github.com/amenonegames/NullObjectGenerator

# 導入方法
上記URLよりDLLをダウンロードし、Unity内に放り込み、設定することで利用できます！
導入方法については、以下記事に詳しく書いておきました！
DL用のURLだけ上記のURLに差し替えてお考えください！

https://qiita.com/amenone_games/private/25c857608af0138eb646#%E5%B0%8E%E5%85%A5

::: note
Unityは　Unity 2021.3　以上、
IDEは以下条件で動作するはずです。

JetBrains Rider Editor v3.0.9以降
Code Editor Package for Visual Studio v2.0.11以降
Code Editor Package for Visual Studio Code v1.2.4以降

利用しているIDEのバージョンによっては追加工程が必要な場合があります(URL内詳述)。
:::


# 使い方

2つの機能を持っているので、順に解説します！

## 指定クラスの全てのInterfaceを継承したNullObjectを作る

一つ目の機能は、Classにアトリビュートをつけて利用します。
アトリビュート名は ```InheritsToNullObj``` です。
利用例は以下の通りです。

```csharp
using NullObjectGenerator;

    [InheritsToNullObj]
    public class TestClass : IHogeInterface , IFugaInterface
    {
        //略
    }
```

その名の通り、
対象クラスが継承している全ての **interface** を継承した、NullObectを生成します。

生成されたクラスの名前は、元々のクラス名の末尾に**AsNullObj**を付したものになります。
上記の例の場合、```TestClassAsNullObj```という名前のクラスが生成されます。

:::note
interfaceによって指定されているプロパティ、メソッドは自動実装します。
返り値がある場合、defaultを返しますが、
返り値がUniTaskの場合のみ、```UniTask.CompletedTask```を返します。
:::

::: note warn
対象になるのはInterfaceのみです。abstractクラスは対象外ですのでご注意ください。
:::

## 指定したInterfaceを一つだけ実装したNullObjectを作る
二つ目の機能は、interfaceにアトリビュートをつけて利用します。
アトリビュート名は ```InterfaceToNullObj``` です。
利用例は以下の通りです。

```csharp
using NullObjectGenerator;

    [InterfaceToNullObj]
    public interface IHogeInterface
    {
        //略
    }
```

生成されたクラスの名前は、元々のクラス名の末尾に**AsNullObj**を付したものになります。
上記の例の場合、```IHogeInterfaceAsNullObj```という名前のクラスが生成されます。

生成するのはクラスなのに ```I``` から始まっているのはちょっとキモチワルイですが、
AsNullObjと合わせるならこのままかなと思い、一旦そのままにしています。
要望があれば変更します…！

:::note
interfaceによって指定されているプロパティ、メソッドは自動実装します。
返り値がある場合、defaultを返しますが、
返り値がUniTaskの場合のみ、```UniTask.CompletedTask```を返します。
:::

::: note warn
対象になるのはInterfaceのみです。abstractクラスは対象外ですのでご注意ください。
:::

# オプション

何もしないとはいえ、さすがに履歴だけは残したい……

そんなケースに備えて、ログを出せるようにしておきました。
どちらのアトリビュートも引数でログの出し方を指定できます。

ログのタイプは以下の通りです！
```csharp
    public enum LogType
    {
        None,     //デフォルト値　ログを出さない
        DebugLog, // UnityEngine.Debug.Logを出す
        DebugLogErr,  //UnityEngine.Debug.LogErrorを出す
        DebugLogWarn, //UnityEngine.Debug.LogWarningを出す
        ThrowException, //System.ExceptionをThrowする
    }
```

使うときには、以下のように指定してください。

```csharp
[InheritsToNullObj(LogType.ThrowException)]
```
```csharp
[InterfaceToNullObj(LogType.DebugLogErr)]
```

# おわりに
ここまでお読みいただき、ありがとうございます！

こんなものが自動生成できるとは、SourceGeneratorはなんとも便利なものです……

また、VContainerに拡張メソッドを作って、
指定クラスが見つからない場合にNullObjectを登録する改造を後日公開したいと思います！

組み合わせるとさらに便利ですので、ぜひストックして通知を受け取ってくださいね！