【C#】Unityでreadonly修飾子が使えない問題を解決する

#`readonly`修飾子

フィールドに`readonly`修飾子を付けて宣言すると、__フィールド初期化子__もしくは__コンストラクタ__でしか値を代入できなくなるため、不用意に値を変更される心配がなくなって非常に安心できます。


```csharp
public class TestClass
{
    private readonly int readonlyNum = 5; //フィールド初期化子での初期化可
    public TestClass()
    {
        readonlyNum = 3; //コンストラクタでの初期化可
    }
    public void SomeMethod()
    {
        //readonlyNum = 10; //代入できない
    }
}
```

ところがUnityでは（というか`MonoBehaviour`なクラスでは）、コンストラクタを定義できないため、`readonly`なフィールドを初期化することができないのです。
これではいつどこで値が書き換えられるか不安で夜も眠れません。

##`Start`メソッド内で`readonly`を初期化したいシチュエーション

別にフィールド初期化子で初期化すればいいじゃんと思うかもしれませんが、`Start`メソッド内で初期化したいシチュエーションは結構あります。
例えば、次のように`SerializeField`で受け取った値を元にして別のクラスを作るような場合などが挙げられます。

```csharp
public class TestMonoBehaviour : MonoBehaviour
{
    [SerializeField]
    private Component m_component1;
    [SerializeField]
    private Component m_component2;

    private readonly IReadOnlyDictionary<ComponentType, Component> m_componentTable;

    private void Start()
    {
        //エラー！Startはコンストラクタではないため値を代入できない
        m_componentTable = new Dictionary<ComponentType, Component>()
        {
            {ComponentType.Type1, m_component1 },
            {ComponentType.Type2, m_component2 }
        };
    }
}
```

`SerializeField`で受け取った値を元にして`Dictionary`を作っているので、フィールド初期化子で値を初期化できません。
このような場合、どうしても`Start`内で初期化する必要が出てきます。

しかし、かといって`readonly`をやめてしまえば、いつかの自分もしくは別の誰かが手が滑って`null`を代入してしまう可能性が残ってしまい、夜も眠れなくなります。

そこで、どうにかして`MonoBehaviour`なクラスで`readonly`を実現する方法を考えました。

#`MonoBehaviour`で`readonly`を実現する`UniReadOnly<T>`クラス

通常の方法では`MonoBehaviour`で`readonly`を使用できないので、同等の機能を実現する`UniReadOnly<T>`クラスを作りました。
このクラスは以下の特徴を持ちます。

- `T`インスタンスを内部にラップする
- 1回のみ`T`で初期化可能
- あとは`T`の読み取り専用プロパティのみを公開する

1回のみ初期化可能で、あとは読み取り専用として機能するので、`readonly`の代わりとして利用できます。

##実装

実装は次の通りです。
そのままコピペするだけで使えます。

```csharp
/// <summary>
/// 一度しか初期化できない値です。
/// readonlyキーワードの代わりに使用します。
/// </summary>
/// <typeparam name="T">ラップする型</typeparam>
public class UniReadOnly<T>
{
    ///// <summary>
    ///// 値を取得します。
    ///// </summary>
    public T Value { get; private set; }
    /// <summary>
    /// 初期化されているかどうかを取得します。
    /// </summary>
    public bool IsInitialized { get; private set; }

    /// <summary>
    /// 値を初期化します。このメソッドは一度しか呼ぶことができません。
    /// </summary>
    /// <param name="value">初期化する値</param>
    /// <exception cref="AlreadyInitializedException">複数回初期化しようとしたときにスローされます。</exception>
    public void Initialize(T value)
    {
        if (IsInitialized) throw new AlreadyInitializedException();
        IsInitialized = true;
        Value = value;
    }

    public override string ToString() => Value.ToString();
    public static implicit operator T(in UniReadOnly<T> readOnly) => readOnly.Value;
}

internal class AlreadyInitializedException : Exception
{
    public AlreadyInitializedException() : base("すでに初期化されています。")
    {
    }
}
```

##使い方

使い方は簡単です。以下のように使用します。

1. `readonly`にしたいオブジェクトを`UniReadOnly<T>`として読み取り専用フィールドに持つ
2. `Initialize`メソッドで値を初期化する
3. `Value`プロパティで値を取り出す

具体的な使用例は以下のようになります。
※処理内容についてのツッコミは勘弁してください

```csharp
public class TestMonoBehaviour : MonoBehaviour
{
    private readonly UniReadOnly<string> m_readonlyString = new UniReadOnly<string>();

    void Start()
    {
        //読み取り専用にする値で初期化する
        m_readonlyString.Initialize("初期化します");
    }

    void Update()
    {
        //値を読み取り専用で取り出す
        Debug.Log(m_readonlyString);
    }
}
```

`Start`メソッド内で`Initialize`メソッドを呼び出し、読み取り専用にする値を設定しています。
これ以降に再び`Initialize`メソッドを呼び出すと、自作例外クラスである`AlreadyInitializedException`がスローされるため、__値が書き換わらないことが保証されます__。

さらに、`UniReadOnly<T>`のインスタンス自体はフィールド初期化子でインスタンス化してしまえば良いので、`readonly`修飾子を付けることができます。
そのため、`UniReadOnly<T>`インスタンス自体を書き換えられる心配もなく、`Initialize`メソッドで設定した`T`インスタンスの不変性[^1]がしっかりと担保されます。

[^1]: 言うまでもありませんが、`T`インスタンスがイミュータブルであることが保証されるという意味ではありません。

以上から、`UniReadOnly<T>`クラスを使用することで、`MonoBehaviour`なクラスでも`readonly`修飾子とほぼ同等の機能を実現することが可能となります。

##`implicit operator`

ただ、通常の`readonly`修飾子を使った方法とは違って、__毎回毎回Valueプロパティから中身にアクセスしなければならないのが面倒__です。
そこで、`UniReadOnly<T>`には`T`への`implicit operator`を実装してあります。

`implicit operator`は聞き慣れない人も多いと思いますが、__暗黙的な型変換を定義することができる演算子__です。
例えば、`double`型の変数に`int`型の値をそのまま入れられるのは、__`double`から`int`へ暗黙的な型変換が行われているから__です。

```csharp
int i = 5;
double d = i; //intからdoubleへの型変換が暗黙的に行われている
```

このように、`UniReadOnly<T>`にも、`UniReadOnly<T>`から`T`への暗黙的な変換を定義しました。これにより、以下のような記述が可能になり、ただの`readonly`フィールドと同じような扱いを可能にしています。

```csharp
UniReadOnly<int> readonlyInt = new UniReadOnly<int>();
readonlyInt.Initialize(5);

int i = readonlyInt; //UniReadOnly<int>からintへの型変換が暗黙的に行われている
```

###`implicit operator`の適用範囲
ただし、`UniReadOnly<T>`から`T`への暗黙的な型変換がいつも都合良く行われるというわけではありません。
以下のような場合、暗黙の型変換が行われず、素直にValueプロパティを使用する羽目になります。

####`T`インスタンス自体を直接使用しない場合
暗黙的な型変換が行われるのは、「__変換先の型インスタンス(`T`)が求められる場面で、変換元のインスタンス(`UniReadOnly<T>`)が代入された場合__」となります。
つまり、以下のように`T`インスタンスを直接使用しない場合は暗黙的な型変換が行われず、エラーとなります。

```csharp
UniReadOnly<string> readonlyStr = new UniReadOnly<string>();
readonlyStr.Initialize("初期値");

int stringLength = m_readonlyString.Length; //エラー！UniReadOnly<string>のLengthプロパティを見に行ってしまう
```

このようなことがしたい場合は、以下のようにきちんと`Value`プロパティを書かなければなりません。

```csharp
UniReadOnly<string> readonlyStr = new UniReadOnly<string>();
readonlyStr.Initialize("初期値");

int stringLength = m_readonlyString.Value.Length;
```

####インターフェイスが絡む場合
インターフェイスが絡む場合も何故か暗黙的な型変換が使用できないみたいです。以下のようなことはできません。

```csharp
UniReadOnly<IEnumerable<int>> uniReadOnlyCollection = new UniReadOnly<IEnumerable<int>>();
uniReadOnlyCollection.Initialize(Enumerable.Range(1, 5));

IEnumerable<int> vs = uniReadOnlyCollection; //エラー！インターフェイスが絡んでいるので暗黙的な型変換が行われない
```

このようなことがしたい場合は、`Value`プロパティを使うか、もしくはキャスト（明示的な型変換）を行う必要があります。

```csharp
UniReadOnly<IEnumerable<int>> uniReadOnlyCollection = new UniReadOnly<IEnumerable<int>>();
uniReadOnlyCollection.Initialize(Enumerable.Range(1, 5));

IEnumerable<int> vs = uniReadOnlyCollection.Value;
IEnumerable<int> vs2 = (IEnumerable<int>)uniReadOnlyCollection;
```

なぜインターフェイスが絡むと暗黙的な型変換が使用できないか、いまいち理由がわかりません。誰か教えて下さい。

###`implicit operator`の是非

`implicit operator`のおかげで、ある場面では通常の読み取り専用フィールドと同様の使用感を得られます。
しかし、また別の場面では`Value`プロパティを介す必要がある場合もあります。

このように、統一した使い方ができないことから、読みづらくなる、可読性が下がると感じる方もいると思います。
僕自身は、そこまで問題とは感じない（むしろそこら中に`.Value`がある方が読みづらいと感じる）のですが、`implicit operator`の実装の是非は好みが分かれるところだと思いますので、好みに応じて調整してください（というか意見いただけると嬉しいです）。

#最後に

`readonly`のためにここまでする必要あるかって意見もあると思いますが、僕はどうしても気になるタチなので専用クラスを作ってしまいました。
賛否両論あると思いますが、よければ使ってみてください。ご意見ご感想あればコメントよろしくお願いします！