【UniRx】IReadOnlyReactiveCollectionに共変性を持たせる

先日、Unityを触っていて次のようなことがしたくなりました。

```csharp
interface IItemInfo{ ... }
class ItemInfo : IItemInfo{ ... }

class TestClass {
    private ReactiveCollection<ItemInfo> m_rxCollection = new ReactiveCollection<ItemInfo>();
    public IReadOnlyReactiveCollection<IItemInfo> ObservableCollection => m_rxCollection;
}
```

あるクラスのインスタンスが格納されたReactiveCollectionをReadOnlyにして公開したいのですが、さらに中身もクラスを直接渡すのではなく、__インターフェイスとして公開したい__というわけです。

でも、普通にやると怒られます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697830/cf6d8571-b583-ed28-e4b8-4f5058e7909f.png)

しかし、例えばList<T>をIReadOnlyList<T>にして公開する場合は怒られません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697830/095ecbf2-6d54-22c5-ae69-3fb162a2131c.png)


これ、なぜかというとIReadOnlyList<T>インターフェイスには__ジェネリクスの共変性__が認められているからです。

共変性については、他のサイトで詳しく解説されていますので割愛しますが、要は__型引数にもポリモーフィズムを適用させられる機能__で、__型引数にout修飾子をつければ共変性が使えます__。

out修飾子は、ジェネリクス引数で指定した型のオブジェクトが変更されないことを保証するので、不変であれば型引数も安全に型変換が可能であるということですね。

詳しい解説は下記サイトが非常に分かりやすくおすすめです。
https://ufcpp.net/study/csharp/sp4_variance.html

で、IReadOnlyListインターフェイスの定義を見てみると、型引数にout修飾子がついています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697830/e24e481c-b6a6-5860-04ef-78d753b34983.png)

対して、IReadOnlyReactiveCollectionインターフェイスの定義を見ると、型引数にout修飾子がついていません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697830/1293e132-6dbe-9af9-64fd-a77e1083a84f.png)

と、いうわけで、IReadOnlyReactiveCollection<T>インターフェイスにも共変性を持たせれば解決するということになります。

#IReadOnlyReactiveCollection<T>に共変性を持たせる

IReadOnlyReactiveCollection<T>のインターフェイスの型引数にout修飾子をつけ、共変性を持たせましょう。
しかし、何も考えずout修飾子をつけると怒られます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697830/9d89054b-8c2d-ca2d-5fd3-b52d16a41126.png)

なぜかというと、IReadOnlyReactiveCollectionインターフェイスのメンバが、Tが不変であると保証していないからです。
例えば、CollectionAddEvent<T>は、TのSetterをもつので、値が書き換えられる可能性があります。
したがって、Tは不変ではないので、エラーが発生しています。

##Tが不変なIReadOnlyCollectionAddEvent<out T>インターフェイスを定義する

となれば、Tが不変であることを保証するCollectionAddEvent<T>のインターフェイスIReadOnlyCollectionAddEvent<out T>を定義しましょう。

```csharp
public interface IReadOnlyCollectionAddEvent<out T>
{
    int Index { get; }
    T Value { get; }
}
```

TのSetterを削除しました。もともとprivate setであったので問題ないでしょう。
こうすることで、IReadOnlyCollectionAddEventインターフェイスを通じて行った操作ではTが不変であることが保証されるので、晴れて共変性が使えるようになります。

##IReadOnlyReactiveCollectionOut<out T>インターフェイスを定義する

IReadOnlyReactiveCollectionインターフェイスを、先ほど定義したIReadOnlyCollectionAddEvent<out T>インターフェイスを使って直接書き換えてはいけません。
インターフェイスのメンバを変更すると、そのインターフェイスを実装したすべてのクラスに影響があるからです。

したがって、共変性が使える特別なIReadOnlyReactiveCollectionOut<out T>インターフェイスを新しく定義しましょう。

```csharp

public interface IReadOnlyReactiveCollectionOut<out T> : IEnumerable<T>
{
    IObservable<IReadOnlyCollectionAddEvent<T>> ObserveAdd();
    IObservable<IReadOnlyCollectionRemoveEvent<T>> ObserveRemove();
}
```

今回は、ObserveAdd()とObserveRemove()しか使用予定がなかったので、とりあえずこの２つを定義しました。
他のメンバも使用する場合は適宜追加してください。

で、この新しいIReadOnlyReactiveCollectionOut<out T>インターフェイスを、ReactiveCollection<T>クラスに実装します。
僕は次のように実装しました。普通のObserveAdd()の戻り値をインターフェイスに変換しているだけです。

```csharp

IObservable<IReadOnlyCollectionAddEvent<T>> IReadOnlyReactiveCollectionOut<T>.ObserveAdd()
    => ObserveAdd().Select(d => (IReadOnlyCollectionAddEvent<T>)d);
IObservable<IReadOnlyCollectionRemoveEvent<T>> IReadOnlyReactiveCollectionOut<T>.ObserveRemove()
    => ObserveRemove().Select(d => (IReadOnlyCollectionRemoveEvent<T>)d);
```

少し長くなってしまいましたが、以上で当初の目的であった「ReactiveCollection<Class>をReadOnlyかつ中身も直接ではなくインターフェイスで公開する」ことができます！

#使ってみる
##問題となったコード

ReactiveCollection<ItemInfo>をReadOnlyかつ中身もインターフェイス公開にしたいのにできません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697830/cb6b6310-0eaf-02fb-a12d-e70bf4f3763d.png)

IReadOnlyReactiveCollection<T>インターフェイスがTの不変性を保証していないからです。

##改善したコード

対して、対策したIReadOnlyReactiveCollectionOut<out T>インターフェイスで公開すると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697830/4d4347ac-9f5b-aa28-6466-9f022db36df6.png)

エラーが出なくなっています！
IReadOnlyReactiveCollectionOut<out T>インターフェイスが、Tの不変性を保証しているからですね。

#最後に
ReactiveCollectionをReadOnlyにして公開することは多々あると思いますが、今回はどうしても中身のクラスもインターフェイスにして公開したかったので無理やり対策してみました。
UniRx標準で対応されていると嬉しいのですが…。

今のところ問題ありませんが、今後不都合など出てきたら記事書こうと思います。