【C#】IReadOnlyList<T>とReadOnlyCollection<T>とImmutableList<T>の違い

## はじめに

先日、下記記事を公開しました。

__【C#】インターフェイスの利点が理解できない人は「インターフェイスには３つのタイプがある」ことを理解しよう__
https://qiita.com/yutorisan/items/d28386f168f2f3ab166d

この記事でIReadOnly系インターフェイスの役割と重要性について簡単に説明し、

>`List`をそのまま渡すのではなく、`IReadOnlyList`として渡すだけで、渡した先で勝手に書き換えられる危険性が皆無になります

と記述しましたが、これに対して「__`IReadOnlyList`では`IList`にキャストされてしまうので、変更される危険性が皆無ではない。本当に皆無にしたければ`ImmutableList`で公開すべき__」とのご意見をいただきました。

`ImmutableList`のことは正直あまり知らなかったので、調べてみると「不変なコレクション」とのこと。
`ReadOnlyCollection`とどう違うんだ…？と思ったので、それぞれ違いを整理しました。

## `IReadOnlyList<T>`インターフェイス
`IReadOnlyList<T>`は、ListのReadOnlyなインターフェイスです。

定義は次のようになっています。

```csharp

public interface IReadOnlyList<out T> : IEnumerable<T>, IEnumerable, IReadOnlyCollection<T>
{
	T this [int index] {
		get;
	}
}
```
`IReadOnlyList<T>`が継承[^1]している`IReadOnlyCollection<T>`の定義はこうです。

[^1]: ちなみにインターフェイスがインターフェイスを「継承」しているっていうんですかね？それとも「実装」？意味合い的には「継承」が正しい気もしますが。

```csharp

public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
{
	int Count {
		get;
	}
}
```

つまり、__インデクサによる値の取得__ と __要素数の取得__ 、それから __`IEnumerator<T>`の取得（foreachで回す）__ だけができるコレクションというわけですね。
`List<T>`を`IReadOnlyList<T>`で渡すと、コレクション変更系メソッドが呼べなくなるので安全ですよ、というインターフェイスです。


### 問題点：キャストされるとReadOnlyじゃなくなる

さて、ここからが本題で、冒頭で記述した「__キャストされたら普通に変更されちゃう問題__」についてです。

`IReadOnlyList<T>`は`List<T>`のインターフェイス[^2]なので、 __`List<T>`にキャストされてしまえば普通に変更できちゃう__ という問題です。

[^2]: もちろん`List<T>`だけじゃなくて色々なクラスに実装されていますが代表として挙げました

以下の例をご覧ください。

```csharp

class Program
{
    static void Main(string[] args)
    {
        IReadOnlyList<int> ireadonlylist = new List<int>() { 1, 2, 3 };

        ReadOnlyBreaker.Break(ireadonlylist);

        foreach (var item in ireadonlylist)
        {
            Console.WriteLine(item);
        }
    }
}

class ReadOnlyBreaker
{
    //IReadOnlyListで受け取っても…
    public static void Break(IReadOnlyList<int> rolist)
    {
        //キャストしてしまえばAddできる
        (rolist as IList<int>).Add(100);
    }
}
```

1,2,3で`List<int>`を初期化して、`IReadOnlyList<int>`にして渡しても、受け取った側が勝手に`IList<int>`にキャストすればAddできてしまうという例です。

実行結果がこちら。

```
1
2
3
100
```
`IReadOnlyList<int>`で渡したのに、100がAddされてしまっています。

これは、`IReadOnlyList<T>`がインターフェイスであるがゆえ、必ず具象クラスが存在するので、もともとの具象クラス側で変更を許可しているならば無理やり変更できるという、悲しい問題です。

しかし、__普通のプログラマーであれば「`IReadOnlyList`」と書いてあれば、「あ、このコレクションは変更してはいけないんだな」と理解できる__ ため、わざわざキャストしてまで無理やり変更されることは少ないと思います。
したがって、決して __`IReadOnlyList<T>`では危険だ！と主張しているわけではありません__ のでご留意ください。

しかしながら、汎用的なライブラリとして公開するなど、どんな使い方をされるかわからない場合や、キャストによる変更される危険もなくしたいと言った場合は、次に紹介する`ReadOnlyCollection<T>`クラスまたは`ImmutableList<T>`クラスを使用すれば、この問題を解決することができます。

## `ReadOnlyCollection<T>`クラス

`ReadOnlyCollection<T>`クラスは、`List<T>`をラップして読み取り専用なメンバのみ外部に公開するクラスです。

このクラスを生成するには`AsReadOnly()`メソッドを利用するか、`IList<T>`を引数にとったコンストラクタを利用します。

```csharp

List<int> list = new List<int>() { 1, 2, 3 };

//AsReadOnly()による生成
ReadOnlyCollection<int> readonlyCollection = list.AsReadOnly();

//コンストラクタによる生成
ReadOnlyCollection<int> readonlyCollection = new ReadOnlyCollection<int>(list);
```

### `IReadOnlyList<T>`との違い

`IReadOnlyList<T>`はインターフェイスで、 __実体としてはあくまでも具象クラスを参照している__ ため、具象クラスにキャストされれば`List<T>`が変更される危険があります。
ところが、この __`ReadOnlyCollecion<T>`はインターフェイスではなく、元の`List<T>`とはまた別のクラス__ なので、キャストされる心配がありません。

具体的には、元となる`List<T>`の参照を内部で持ち、読み取り専用のメンバのみを外部に公開しています。
そのため、内部の`List<T>`は完全にプロテクトされており、変更される危険がないのです。

#### 図による解説

ちょっと分かりづらいかと思いますので図を用意しました。
`IReadOnlyList<T>`の場合は次のようになります。

![IReadOnlyList.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697830/19458827-e2f6-1159-a20f-a4743a4f97f7.png)

`IReadOnlyList<T>`は`List<T>`のうち読み取り専用のメンバのみを提供するインターフェイスです。
しかし、あくまでも`List<T>`に実装されたインターフェイスなので、キャストされれば`List<T>`に直接アクセスされます。


対して、`ReadOnlyCollection<T>`は次のようになります。
![ReadOnlyCollection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697830/71508a34-c281-1be9-9cf0-5a939f01e79a.png)

`ReadOnlyCollection<T>`は、インターフェイスではなく実体があるクラスで、内部に元となる`List<T>`の参照を持っています。
`ReadOnlyCollection<T>`は`List<T>`のメンバのうち読み取り専用メンバのみを外部に公開するため、`List<T>`自体は安全が保たれます。
さらに、外部からアクセスされる`ReadOnlyCollection<T>`は内部の`List<T>`とは独立した存在なので、キャストによって内部の`List<T>`が変更される危険はありません。


## `ImmutableList<T>`クラス

`ImmutableList<T>`クラスも、`ReadOnlyCollection<T>`と同様に、元となる`List<T>`を内部にラップします。

このクラスを生成するには、`ToImmutable()`メソッドを利用するか、`ImmutableList.CreateRange(IEnumerable<T>)`ファクトリメソッドを利用します。コンストラクタによる生成はできません。

```csharp

List<int> list = new List<int>() { 1, 2, 3 };

//ToImmutable()による生成
ImmutableList<int> immutableList = list.ToImmutable();

//ImmutableList.CreateRangeによる生成
ImmutableList <int> immutableList = ImmutableList.CreateRange(list);
```

### 参照ではなくコピーをラップする
`ReadOnlyCollection<T>`との違いは、 __参照ではなくコピーをラップする__ ことです。
参照ではなくコピーをラップするため、`ImmutableList<T>`を生成したあとに元となるコレクションに変更を加えても、`ImmutableList<T>`の読み出し値は変わりません。

以下のコードで、`ReadOnlyCollection<T>`と`ImmutableList<T>`を両方生成したあとに、元のコレクションに変更を加えて、動作の違いを確認してみます。

```csharp
static void Main(string[] args)
{
    List<int> list = new List<int>() { 1, 2, 3 };

    ReadOnlyCollection<int> readonlyCollection = list.AsReadOnly();
    ImmutableList<int> immutableList = list.ToImmutableList();

    //ReadOnlyCollection, ImmutableListを生成したあとに元となるコレクションに変更を加える
    list.Add(100);

    Console.WriteLine("ReadOnlyCollection:");
    foreach (var item in readonlyCollection)
    {
        Console.WriteLine(item);
    }
    Console.WriteLine("ImmutableList:");
    foreach (var item in immutableList)
    {
        Console.WriteLine(item);
    }
}
```

```
ReadOnlyCollection:
1
2
3
100
ImmutableList:
1
2
3
```

`ReadOnlyCollection<T>`のほうは、インスタンス生成後に元となるコレクションに変更が加えられた場合でも、その変更内容が反映されています。元となるコレクションの参照を内部にラップしているからですね。
対して、`ImmutableList<T>`のほうは、インスタンス生成後の変更内容が反映されていません。これは、元となるコレクションのコピーを内部にラップしているためです。

言うならば、 __コレクションのスナップショットを撮影しているような感覚__ だと思います。

### 変更系メソッドが利用可能
`ImmutableList<T>`の場合は __変更系メソッドが呼び出し可能__ です。
変更系メソッドを呼び出した場合、内部にラップされた元となる`List<T>`のコピーがまた生成され、そのコピーに対して変更内容が適用されて、メソッドの戻り値として返されます。

したがって、変更系メソッドを呼び出した場合でも、元となるコレクションはもちろん、内部にラップしたコレクションにも変更が加えられることはありません。

### 図による解説

`ImmutableList<T>`の挙動を図で表すと次のようになります。
![ImmutableList.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697830/c1ca644b-71f4-c190-ffb2-6a9df565b7f5.png)

`ReadOnlyCollection<T>`と同様、キャストによる変更の危険性はなくなっています。

## まとめ

`IReadOnlyList<T>`, `ReadOnlyCollection<T>`, `ImmutableList<T>`の違いをまとめると次のようになります。

| |`IReadOnlyList<T>`|`ReadOnlyCollection<T>`|`ImmutableList<T>`|
|---|---|---|---|
|種類|インターフェイス|クラス|クラス|
|利用方法|そのまま渡せばOK|`AsReadOnly()`<br>または<br>`new ReadOnlyCollection(IList<T>)`|`ToImmutable()`<br>または<br>`ImmutableList.CreateRange(IEnumerable<T>)`|
|元コレクション|直接参照している|参照がラップされる|コピーがラップされる|
|変更系メソッド|__利用不能__<br>（ただしキャストで利用可能）|__利用不能__|__利用可能__<br>ただしコピーに適用され、ソースには影響しない|
|利用場面|「この`List`は変更してほしくない」という意思を伝えたい|`List`を絶対に変更できない形で渡したい|コレクションのスナップショットを渡したい|

### メソッド名から伝わるニュアンス
`AsReadOnly()`と`ToImmutable()`のメソッド名をこうやって比べてみると、確かにAsの方は「オブジェクト参照はそのままで、型を変化させる」といったニュアンスが伝わりますし、Toの方は「オブジェクトを元にして別のインスンタンスを生成する」といったニュアンスが伝わります。細かなメソッド名の違いも、その挙動を正確に表現しているようです。メソッド名はきちんと考えて付けなければならないと感じました。

## おまけ：`IList<T>`インターフェイスの実装について

実は、`ReadOnlyCollection<T>`クラスと`ImmutableList<T>`クラスは、共に`IList<T>`インターフェイスを実装しています。
`IList<T>`インターフェイスを実装しているということは、`Add`や`Remove`ができるはずですよね？

すごく疑問に思い調べてみると、 __インターフェイスの明示的な実装__ というものを使っているらしいです。

### インターフェイスの明示的な実装
詳しくは[このサイト](https://ufcpp.net/study/csharp/oo_interface.html?p=4)を参考にしてほしいのですが、インターフェイスのメンバにあるけども、`public`にしたくないメンバを隠すために使われることがあるようです。

インターフェイスの明示的な実装を行うには下記のよう、`インターフェイス名.メンバ名`でインターフェイスメンバを実装します。

```csharp

public interface ITestInterface 
{
    void MethodA();
    void MethodB();
}

public class TestClass : ITestInterface 
{
    //普通のインターフェイス実装
    public void MethodA()
    {

    }

    //明示的なインターフェイスの実装
    void ITestInterface.MethodB()
    {

    }    
}

```

このようにすると、`MethodB`は`TestClass`クラスのインスタンスから呼ぶことはできなくなり、メンバを隠すことができます。

しかし、インターフェイスを実装している以上、メンバは呼べなければなりません。
ではどうすれば呼べるかと言うと、`インターフェイス名.メンバ名`で呼ぶことができます。
実装しているクラスのインスタンスから直接呼び出すことはできないけども、インターフェイス経由ならば呼び出せるようです。

「インターフェイスの明示的な実装」、使う場面にはあまり出くわしたことがないですが、まだ知らないことがたくさんあると勉強になりました。

### 呼び出すと必ず`NotSupportedException`がスローされる

では、`ReadOnlyCollection<T>`や`ImmutableList<T>`でも、`IList<T>`インターフェイス経由であれば`Add`や`Remove`ができるのかといえば、そうではないようです。

両者のクラスは、 __`IList<T>`インターフェイス経由で変更系メソッドを呼び出すと、必ず`NotSupportedException`がスローされる__ ようです。

この実装って正しいんですかね…？そこまでして`IList<T>`インターフェイスを実装する理由は…？
まぁ、マイクロソフトのやることなので正しいのでしょう（適当）

誰か`ReadOnlyCollection<T>`や`ImmutableList<T>`が`IList<T>`を実装してる理由知っていたら教えて下さい。


以上、長くなってしまいましたが`IReadOnlyList<T>`と`ReadOnlyCollection<T>`と`ImmutableList<T>`の違いでした。