LoginSignup
26
18

使いこなせていなかった`System.Collections.Immutable`

Last updated at Posted at 2018-05-27

はじめに

Collections系の記事を書こうとしていたら、System.Collections.Immutable名前空間の型をまったく使えていなかったことに気づき、反省も兼ねて記事にしてみました。

仕様は調べて書いているつもりですが、 使いこなせていない人(=私)の記事ですので、実戦で使いこなしている方からの温かいツッコミを期待しています!

Immutable(イミュータブル/変化しない)

まずは_Immutable_なデータ型の代表的な特徴は以下の通り。

  • 内容に変更がない場合の複製のコストが低い
  • スレッドセーフ

これを意識したクラスを自分で作ったことがあるかどうかはさておき、使ったことがない人はいないと思います。
なぜならば、C#では string型はImmutable だからです。
「Immutableってなんだったっけ?」と思ったら、string型を思い出すと良いでしょう。

クラスライブラリの中身

System.Collections.Immutable名前空間には、一通りのコレクション系クラスが揃っています。

種別 整列
ImmutableArray<T> 配列
ImmutableList<T> List
ImmutableHashSet<T> Set ×
ImmutableSortedSet<T> Set
ImmutableQueue<T> Queue
ImmutableStack<T> Stack
ImmutableDictionary<TKey,TValue> Dictionary ×
ImmutableSortedDictionary<TKey,TValue> Dictionary

インスタンスの生成

これらのクラスはコンストラクタ持ちませんが、C#12 以降では、コレクション式で生成できます。
C#11 以前の環境では、対応する静的クラスのファクトリメソッドCreate<T>()からインスタンス生成をしましょう。

Immutableなリストの生成 (C# 11 以前)
var list = ImmutableList.Create<int>(2, 3, 5, 7);
《参考》MutableなListの生成 (C# 11 以前)
var list = new List<int>() { 2, 3, 5, 7 };
各リストの生成 (C# 12 以降)
// 「コレクション式」構文により、
// 統一感のある書き方ができるようになった。
ImmutableList<int> ilist = [2, 3, 5, 7];
List<int> list = [2, 3, 5, 7];
int[] array = [2, 3, 5, 7];
ReadOnlySpan<int> span = [2, 3, 5, 7];

コレクションの操作

同じようなメソッド名のコレクション操作を持ちますが、操作後は別のインスタンスが生成されることに注意します。

ImmutableなListに項目追加
// ImmutableなListは、項目が追加された別のインスタンスを生成する
var addedList = list.Add(11);
ImmutableなListに項目追加 (C# 12 以降)
// 以下の書き方でも末尾に項目追加可能。
// パフォーマンスは不明
list = [.. list, 11];
《参考》MutableなListに項目追加
// MutableなListは、自身の中身を書き換える
list.Add(11);

いざ使うときに左辺を書き忘れそうで怖い……

Builder

不変なデータ型を扱うときに欠かせない、BuilderがそれぞれのImmutableコレクション型に存在しており、ToBuilder()メソッドでBuilderに、ToImmutable()メソッドで元のImmutableコレクションに、それぞれ移行できます。
ちょうど、String型に対する StringBuilder型のようなものだと思えば、しっくりくると思います。

Builderの使い方
// var originalList = ImmutableList.Create<int>(2, 3, 5, 7); (C# 11 以前)
ImmutableList<int> originalList = [2, 3, 5, 7];
// 大量の変更が必要な場合、Builderに。
var builder = originalList.ToBuilder();
builder.Add(11);
builder.Add(13);
builder.Remove(2);
// 変更後、ToImmutable()で、Immutableに。
var newList = builder.ToImmutable();
《参考》StringBuilderに似ている
var originalString = "ABCDEFG";
// 大量の変更が必要な場合、Builderに。
var builder = new StringBuilder(originalString);
builder.Append("H");
builder.Append("I");
builder.Remove(0, 1);
// Immutableに。
var newStr = builder.ToString();

初めからBuilderを生成するためのファクトリメソッドもあります。

初めからBuilderを生成する
var builder = ImmutableList.CreateBuilder<int>();

どんな時に使う?

"マルチスレッドな操作で" "コレクションそのものを扱うとき" Immutableなコレクションが生きてくると思います。同様にマルチスレッド操作で登場するConcurrentなコレクションとは役割が違うので、それぞれを適した場面で使うことになります。
また、ReadOnlyなインタフェースとも似ているようですが、出番が全く違います。

性質
System.Collections.Immutable.
ImmutableHogehoge
不変だが、コレクション操作は行う
コレクション全体をスレッドセーフに扱いたい
System.Collections.Concurrent.
ConcurrentHogehoge
可変
コレクション操作をスレッドセーフに扱いたい
System.Collections.Generic.
IReadOnlyHogehoge
コレクション操作を行わない
スレッドセーフかは実装依存(スレッドセーフとはいえない)

まとめ

System.Collections.Immutable名前空間がマルチスレッドの全てを解決するわけではないですが、この存在を認識していれば 必ず活躍する場がある と思います。実際に使用する際にはImmutableInterlockedクラスなども合わせて、うまく組み立てる必要がありそうですので、試行錯誤しつつ使っていきたいと思います。

26
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
18