HashSetとSortedSet

System.Collections.Generic.HashSetクラスSystem.Collections.Generic.SortedSetクラスは、数学における集合の概念を表すコレクションクラスです。 HashSetは.NET Framework 3.5 SP1、SortedSetは.NET Framework 4より提供されています。

HashSetクラス・SortedSetクラスを使うことで、一方のコレクションが他方のコレクションと重複する部分を持つか、あるいは完全に異なるか、他方を内包するか、といったコレクション同士の包含関係を調べることができます。 (§.包含関係の検証) また、コレクション同士の和(OR)を求めて二つのコレクションをマージしたり、積(AND)を求めて共通する要素だけを残したりすることができます。 (§.集合演算)

HashSet・SortedSetでは、要素は重複することなく格納されます。 Listなどとは異なり既に追加されている要素を追加しようとしても重複して格納されることなく、常に単一の要素として扱われます。 そのため、HashSet・SortedSetはキーだけを扱う一種のDictionaryのようなコレクションと見ることもできます。 一方、Add/Remove/Clearなど要素を動的に追加・削除するメソッドや、foreachによる列挙などもサポートされているため、その点ではList等の他のコレクションと同様に扱うこともできます。 (§.基本操作)

SortedSetクラスは、名前のとおりHashSetクラスに並べ替えの機能を追加したクラスで、要素を追加したり列挙したりする際には自動的にソートされます。 ほかにも、集合内における最大値・最小値の取得ができるなど、SortedSetでのみ提供される機能も存在します。

.NET Framework 4以降ではHashSet・SortedSetはともにISet<T>インターフェイスを実装しています。 .NET Framework 3.5ではISet<T>は存在せず、そのためHashSetもISet<T>を実装していないので注意してください。 また.NET 5以降では、ISet<T>に対応する読み取り専用インターフェイスIReadOnlySet<T>インターフェイスが新たに導入されるため、HashSet・SortedSetを含むISetを読み取り専用で扱うことができるようになります。

System.Collections名前空間ではHashSetクラス・SortedSetクラスに相当するクラス、集合の概念を表す非ジェネリックなコレクションクラスは提供されていません。

基本操作

HashSetクラス・SortedSetクラスともに、Listなどの他のコレクションクラスと共通するAdd, Remove, Contains, Clearなどのメソッドが用意されています。 IEnumerable<T>インターフェイスを実装しているため、foreach/For Eachステートメントで要素を列挙することもできます。

そのため、重複する値が単一の要素として扱われる以外は、他のコレクションクラスと同様に扱うことができます。 また、SortedSetでは自動的に並べ替えが行われる点を除けば、動作と結果もHashSetと同じです。

HashSet
HashSetでの要素の追加・削除・検索
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new HashSet<int>() {3, 1, 6, 4, 0};

    Console.WriteLine(string.Join(", ", s));

    // 要素を追加
    s.Add(2);

    Console.WriteLine(string.Join(", ", s));

    // 要素を削除
    s.Remove(6);

    Console.WriteLine(string.Join(", ", s));

    // 値5が含まれているか
    Console.WriteLine("Contains 5? {0}", s.Contains(5));
  }
}
HashSetでの要素の追加・削除・検索
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New HashSet(Of Integer) From {3, 1, 6, 4, 0}

    Console.WriteLine(String.Join(", ", s))

    ' 要素を追加
    s.Add(2)

    Console.WriteLine(String.Join(", ", s))

    ' 要素を削除
    s.Remove(6)

    Console.WriteLine(String.Join(", ", s))

    ' 値5が含まれているか
    Console.WriteLine("Contains 5? {0}", s.Contains(5))
  End Sub
End Class
実行結果
3, 1, 6, 4, 0
3, 1, 6, 4, 0, 2
3, 1, 4, 0, 2
Contains 5? False
SortedSet
SortedSetでの要素の追加・削除・検索
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<int>() {3, 1, 6, 4, 0};

    Console.WriteLine(string.Join(", ", s));

    // 要素を追加
    s.Add(2);

    Console.WriteLine(string.Join(", ", s));

    // 要素を削除
    s.Remove(6);

    Console.WriteLine(string.Join(", ", s));

    // 値5が含まれているか
    Console.WriteLine("Contains 5? {0}", s.Contains(5));
  }
}
SortedSetでの要素の追加・削除・検索
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of Integer) From {3, 1, 6, 4, 0}

    Console.WriteLine(String.Join(", ", s))

    ' 要素を追加
    s.Add(2)

    Console.WriteLine(String.Join(", ", s))

    ' 要素を削除
    s.Remove(6)

    Console.WriteLine(String.Join(", ", s))

    ' 値5が含まれているか
    Console.WriteLine("Contains 5? {0}", s.Contains(5))
  End Sub
End Class
実行結果
0, 1, 3, 4, 6
0, 1, 2, 3, 4, 6
0, 1, 2, 3, 4
Contains 5? False

列挙操作

HashSet・SortedSetともに、foreach/For Eachステートメントで要素を列挙することができます。 HashSetでは、Listと同様に要素を追加した順に列挙されますが、SortedSetでは値の大小関係に従って小さい順に列挙されます。

HashSet
HashSetでの列挙操作と列挙順
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new HashSet<int>() {3, 1, 6, 4, 0};

    // 列挙して要素を表示
    foreach (var e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();

    // 要素を追加
    s.Add(2);

    // 列挙して要素を表示
    foreach (var e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
HashSetでの列挙操作と列挙順
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New HashSet(Of Integer) From {3, 1, 6, 4, 0}

    ' 列挙して要素を表示
    For Each e As Integer In s
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()

    ' 要素を追加
    s.Add(2)

    ' 列挙して要素を表示
    For Each e As Integer In s
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
3, 1, 6, 4, 0, 
3, 1, 6, 4, 0, 2, 
SortedSet
SortedSetでの列挙操作と列挙順
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<int>() {3, 1, 6, 4, 0};

    // 列挙して要素を表示
    foreach (var e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();

    // 要素を追加
    s.Add(2);

    // 列挙して要素を表示
    foreach (var e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
SortedSetでの列挙操作と列挙順
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of Integer) From {3, 1, 6, 4, 0}

    ' 列挙して要素を表示
    For Each e As Integer In s
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()

    ' 要素を追加
    s.Add(2)

    ' 列挙して要素を表示
    For Each e As Integer In s
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
0, 1, 3, 4, 6, 
0, 1, 2, 3, 4, 6, 

Reverseメソッドを使うと、逆順(値の大きい順)で列挙することができます。

重複する要素の追加

HashSet・SortedSetでは同じ値の要素を複数回格納しようとしても重複して格納されることはなく、常に1つだけが格納されます。 この点はDictionaryのキーと似た動作と言えますが、すでに存在する状態で追加しようとしても例外がスローされることはありません。

HashSet・SortedSetともに同じ要素が重複した状態になることはありませんが、HashSetでは格納した時の順序は維持されます(最初に格納された順になります)。

HashSet
HashSetに重複する要素を追加する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new HashSet<int>();

    // 同じ要素を複数回追加
    // (同じ値がすでに存在する場合は、追加されない)
    s.Add(3);
    s.Add(3);
    s.Add(1);
    s.Add(1);
    s.Add(2);
    s.Add(3);

    Console.WriteLine(string.Join(", ", s));
  }
}
HashSetに重複する要素を追加する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New HashSet(Of Integer)()

    ' 同じ要素を複数回追加
    ' (同じ値がすでに存在する場合は、追加されない)
    s.Add(3)
    s.Add(3)
    s.Add(1)
    s.Add(1)
    s.Add(2)
    s.Add(3)

    Console.WriteLine(String.Join(", ", s))
  End Sub
End Class
実行結果
3, 1, 2
SortedSet
SortedSetに重複する要素を追加する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<int>();

    // 同じ要素を複数回追加
    // (同じ値がすでに存在する場合は、追加されない)
    s.Add(3);
    s.Add(3);
    s.Add(1);
    s.Add(1);
    s.Add(2);
    s.Add(3);

    Console.WriteLine(string.Join(", ", s));
  }
}
SortedSetに重複する要素を追加する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of Integer)()

    ' 同じ要素を複数回追加
    ' (同じ値がすでに存在する場合は、追加されない)
    s.Add(3)
    s.Add(3)
    s.Add(1)
    s.Add(1)
    s.Add(2)
    s.Add(3)

    Console.WriteLine(String.Join(", ", s))
  End Sub
End Class
実行結果
1, 2, 3

要素取得の試行・実際の値の取得 (TryGetValue)

HashSet・SortedSetに特定の要素が含まれているかどうかを調べるにはContainsメソッドを使うことができます。 このほかに、.NET Standard 2.1/.NET Framework 4.7.2/.NET Core 2.0以降では、TryGetValueメソッドを使うこともできます。

TryGetValueメソッドでは、HashSet・SortedSetに指定した値の要素があるかどうかを調べ、ある場合はその値を取得することができます。

HashSet
Contains・TryGetValueメソッドでHashSetに指定した値の要素があるかどうか調べる .NET Standard 2.1/.NET Core 2.0/.NET Framework 4.7.2
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new HashSet<int>() {3, 1, 6, 4, 0};

    // 値4が含まれているかContainsメソッドで調べる
    Console.WriteLine(
      "Contains 4? {0}",
      s.Contains(4)
    );

    // 値4が含まれているかTryGetValueメソッドで調べる
    // (含まれている場合、その値をoutパラメータで取得することもできる)
    Console.WriteLine(
      "TryGetValue 4? {0}",
      s.TryGetValue(4, out var val)
    );
  }
}
Contains・TryGetValueメソッドでHashSetに指定した値の要素があるかどうか調べる .NET Standard 2.1/.NET Core 2.0/.NET Framework 4.7.2
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New HashSet(Of Integer) From {3, 1, 6, 4, 0}

    ' 値4が含まれているかContainsメソッドで調べる
    Console.WriteLine(
      "Contains 4? {0}",
      s.Contains(4)
    )

    ' 値4が含まれているかTryGetValueメソッドで調べる
    ' (含まれている場合、その値をoutパラメータで取得することもできる)
    Dim val As Integer

    Console.WriteLine(
      "TryGetValue 4? {0}",
      s.TryGetValue(4, val)
    )
  End Sub
End Class
実行結果
Contains 4? True
TryGetValue 4? True
SortedSet
Contains・TryGetValueメソッドでSortedSetに指定した値の要素があるかどうか調べる .NET Standard 2.1/.NET Core 2.0/.NET Framework 4.7.2
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<int>() {3, 1, 6, 4, 0};

    // 値4が含まれているかContainsメソッドで調べる
    Console.WriteLine(
      "Contains 4? {0}",
      s.Contains(4)
    );

    // 値4が含まれているかTryGetValueメソッドで調べる
    // (含まれている場合、その値をoutパラメータで取得することもできる)
    Console.WriteLine(
      "TryGetValue 4? {0}",
      s.TryGetValue(4, out var val)
    );
  }
}
Contains・TryGetValueメソッドでSortedSetに指定した値の要素があるかどうか調べる .NET Standard 2.1/.NET Core 2.0/.NET Framework 4.7.2
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of Integer) From {3, 1, 6, 4, 0}

    ' 値4が含まれているかContainsメソッドで調べる
    Console.WriteLine(
      "Contains 4? {0}",
      s.Contains(4)
    )

    ' 値4が含まれているかTryGetValueメソッドで調べる
    ' (含まれている場合、その値をoutパラメータで取得することもできる)
    Dim val As Integer

    Console.WriteLine(
      "TryGetValue 4? {0}",
      s.TryGetValue(4, val)
    )
  End Sub
End Class
実行結果
Contains 4? True
TryGetValue 4? True

ContainsメソッドとTryGetValueメソッドは、どちらもHashSet・SortedSet内に指定した値の要素があるかどうかを調べるという点では同じです。 一方TryGetValueメソッドは、要素がある場合には実際にHashSet・SortedSet内に格納されている値を取得できる点が異なります。

TryGetValueメソッドは、IEqualtyComparer<T>を指定してHashSetを構築した場合、またはIComparer<T>を指定してSortedSetを構築した場合などにおいて、単に値が含まれているかどうかだけでなく、実際に格納されている値を知りたい場合に有効に機能します。

例えば、大文字小文字の違いを無視するHashSet・SortedSetを構築したときに、HashSet・SortedSetにALICEという値が含まれているか調べつつ、実際に格納されている値が何なのか(Aliceまたはalice、もしくはそれ以外なのか)を知りたい、といった場合には、ContainsメソッドではなくTryGetValueメソッドを使うことができます。

HashSet
TryGetValueメソッドでHashSetに指定した値の要素があるかどうか調べ、実際に格納されている値を取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 大文字小文字の違いを無視するHashSet
    var caseInsensitiveSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
      "Alice", "Bob", "Charlie", "Dave", "Eve"
    };

    // 文字列"ALICE"が含まれているかどうか調べる
    // 同時にHashSetに格納されている実際の綴りも取得する
    var alice = "ALICE";

    if (caseInsensitiveSet.TryGetValue(alice, out alice))
      Console.WriteLine(alice);

    // 文字列"cHArLIe"が含まれているかどうか調べる
    // 同時にHashSetに格納されている実際の綴りも取得する
    var charlie = "cHArLIe";

    if (caseInsensitiveSet.TryGetValue(charlie, out charlie))
      Console.WriteLine(charlie);
  }
}
TryGetValueメソッドでHashSetに指定した値の要素があるかどうか調べ、実際に格納されている値を取得する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    ' 大文字小文字の違いを無視するHashSet
    Dim caseInsensitiveSet As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase) From {
      "Alice", "Bob", "Charlie", "Dave", "Eve"
    }

    ' 文字列"ALICE"が含まれているかどうか調べる
    ' 同時にHashSetに格納されている実際の綴りも取得する
    Dim alice As String = "ALICE"

    If caseInsensitiveSet.TryGetValue(alice, alice) Then
      Console.WriteLine(alice)
    End If

    ' 文字列"cHArLIe"が含まれているかどうか調べる
    ' 同時にHashSetに格納されている実際の綴りも取得する
    Dim charlie As String = "cHArLIe"

    If caseInsensitiveSet.TryGetValue(charlie, charlie) Then
      Console.WriteLine(charlie)
    End If
  End Sub
End Class
実行結果
Alice
Charlie
SortedSet
TryGetValueメソッドでSortedSetに指定した値の要素があるかどうか調べ、実際に格納されている値を取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 大文字小文字の違いを無視するSortedSet
    var caseInsensitiveSet = new SortedSet<string>(StringComparer.OrdinalIgnoreCase) {
      "Alice", "Bob", "Charlie", "Dave", "Eve"
    };

    // 文字列"ALICE"が含まれているかどうか調べる
    // 同時にSortedSetに格納されている実際の綴りも取得する
    var alice = "ALICE";

    if (caseInsensitiveSet.TryGetValue(alice, out alice))
      Console.WriteLine(alice);

    // 文字列"cHArLIe"が含まれているかどうか調べる
    // 同時にSortedSetに格納されている実際の綴りも取得する
    var charlie = "cHArLIe";

    if (caseInsensitiveSet.TryGetValue(charlie, out charlie))
      Console.WriteLine(charlie);
  }
}
TryGetValueメソッドでSortedSetに指定した値の要素があるかどうか調べ、実際に格納されている値を取得する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    ' 大文字小文字の違いを無視するSortedSet
    Dim caseInsensitiveSet As New SortedSet(Of String)(StringComparer.OrdinalIgnoreCase) From {
      "Alice", "Bob", "Charlie", "Dave", "Eve"
    }

    ' 文字列"ALICE"が含まれているかどうか調べる
    ' 同時にSortedSetに格納されている実際の綴りも取得する
    Dim alice As String = "ALICE"

    If caseInsensitiveSet.TryGetValue(alice, alice) Then
      Console.WriteLine(alice)
    End If

    ' 文字列"cHArLIe"が含まれているかどうか調べる
    ' 同時にSortedSetに格納されている実際の綴りも取得する
    Dim charlie As String = "cHArLIe"

    If caseInsensitiveSet.TryGetValue(charlie, charlie) Then
      Console.WriteLine(charlie)
    End If
  End Sub
End Class
実行結果
Alice
Charlie

HashSetにIEqualtyComparer<T>を指定して構築する例については§.HashSetでの等価性比較のカスタマイズ、SortedSetにIComparer<T>を指定して構築する例については§.SortedSetでの大小比較のカスタマイズを参照してください。

集合演算

HashSetクラス・SortedSetクラスには集合演算を行うメソッドとして次のメソッドが用意されています。 いずれも引数としてIEnumerable<T>を取り、戻り値はありません(void)。 したがって、これらのメソッドでは引数に指定された配列やコレクションなどとの演算結果をインスタンス自身に反映します。

集合演算を行うメソッド
メソッド 動作
HashSet.IntersectWith
SortedSet.IntersectWith
現在の集合と引数で指定されたIEnumerable<T>との積集合を求める
(引数の集合と共通する要素のみを残す)
HashSet.UnionWith
SortedSet.UnionWith
現在の集合と引数で指定されたIEnumerable<T>との和集合を求める
(引数の集合の要素をマージする)
HashSet.ExceptWith
SortedSet.ExceptWith
現在の集合と引数で指定されたIEnumerable<T>との差集合を求める
(引数の集合の要素を除外する)
HashSet.SymmetricExceptWith
SortedSet.SymmetricExceptWith
現在の集合と引数で指定されたIEnumerable<T>との対称差を求める
(引数の集合と共通する要素のみを除外する)

以下は、上記の集合演算を使った例です。

HashSet
HashSetで積集合・和集合・差集合・対象差を求める
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var set1 = new int[] {6, 2, 0, 4, 8};
    var set2 = new int[] {3, 1, 2, 0, 4};

    // 積集合を求める(IntersectWith)
    var s1 = new HashSet<int>(set1);

    s1.IntersectWith(set2);

    Console.WriteLine(string.Join(", ", s1));

    // 和集合を求める(UnionWith)
    var s2 = new HashSet<int>(set1);

    s2.UnionWith(set2);

    Console.WriteLine(string.Join(", ", s2));

    // 差集合を求める(ExceptWith)
    var s3 = new HashSet<int>(set1);

    s3.ExceptWith(set2);

    Console.WriteLine(string.Join(", ", s3));

    // 対象差を求める(SymmetricExceptWith)
    var s4 = new HashSet<int>(set1);

    s4.SymmetricExceptWith(set2);

    Console.WriteLine(string.Join(", ", s4));
  }
}
HashSetで積集合・和集合・差集合・対象差を求める
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim set1() As Integer = New Integer() {6, 2, 0, 4, 8}
    Dim set2() As Integer = New Integer() {3, 1, 2, 0, 4}

    ' 積集合を求める(IntersectWith)
    Dim s1 As New HashSet(Of Integer)(set1)

    s1.IntersectWith(set2)

    Console.WriteLine(String.Join(", ", s1))

    ' 和集合を求める(UnionWith)
    Dim s2 As New HashSet(Of Integer)(set1)

    s2.UnionWith(set2)

    Console.WriteLine(String.Join(", ", s2))

    ' 差集合を求める(ExceptWith)
    Dim s3 As New HashSet(Of Integer)(set1)

    s3.ExceptWith(set2)

    Console.WriteLine(String.Join(", ", s3))

    ' 対象差を求める(SymmetricExceptWith)
    Dim s4 As New HashSet(Of Integer)(set1)

    s4.SymmetricExceptWith(set2)

    Console.WriteLine(String.Join(", ", s4))
  End Sub
End Class
実行結果
2, 0, 4
6, 2, 0, 4, 8, 3, 1
6, 8
6, 8, 3, 1
SortedSet
SortedSetで積集合・和集合・差集合・対象差を求める
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var set1 = new int[] {6, 2, 0, 4, 8};
    var set2 = new int[] {3, 1, 2, 0, 4};

    // 積集合を求める(IntersectWith)
    var s1 = new SortedSet<int>(set1);

    s1.IntersectWith(set2);

    Console.WriteLine(string.Join(", ", s1));

    // 和集合を求める(UnionWith)
    var s2 = new SortedSet<int>(set1);

    s2.UnionWith(set2);

    Console.WriteLine(string.Join(", ", s2));

    // 差集合を求める(ExceptWith)
    var s3 = new SortedSet<int>(set1);

    s3.ExceptWith(set2);

    Console.WriteLine(string.Join(", ", s3));

    // 対象差を求める(SymmetricExceptWith)
    var s4 = new SortedSet<int>(set1);

    s4.SymmetricExceptWith(set2);

    Console.WriteLine(string.Join(", ", s4));
  }
}
SortedSetで積集合・和集合・差集合・対象差を求める
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim set1() As Integer = New Integer() {6, 2, 0, 4, 8}
    Dim set2() As Integer = New Integer() {3, 1, 2, 0, 4}

    ' 積集合を求める(IntersectWith)
    Dim s1 As New SortedSet(Of Integer)(set1)

    s1.IntersectWith(set2)

    Console.WriteLine(String.Join(", ", s1))

    ' 和集合を求める(UnionWith)
    Dim s2 As New SortedSet(Of Integer)(set1)

    s2.UnionWith(set2)

    Console.WriteLine(String.Join(", ", s2))

    ' 差集合を求める(ExceptWith)
    Dim s3 As New SortedSet(Of Integer)(set1)

    s3.ExceptWith(set2)

    Console.WriteLine(String.Join(", ", s3))

    ' 対象差を求める(SymmetricExceptWith)
    Dim s4 As New SortedSet(Of Integer)(set1)

    s4.SymmetricExceptWith(set2)

    Console.WriteLine(String.Join(", ", s4))
  End Sub
End Class
実行結果
0, 2, 4
0, 1, 2, 3, 4, 6, 8
6, 8
1, 3, 6, 8

SortedSetで並べ替えが行われる以外は、結果は同じです。

以下の図は、上記のHashSet・SortedSetの集合演算メソッドの実行結果を図式化したものです。

IntersectWithの結果
0 1 3 2 4 6 8 set1 set2
UnionWithの結果
0 1 3 2 4 6 8 set1 set2
ExceptWithの結果
0 1 3 2 4 6 8 set1 set2
SymmetricExceptWithの結果
0 1 3 2 4 6 8 set1 set2

LINQの拡張メソッドを使った集合演算

LINQの拡張メソッドを使うことでも、HashSet・SortedSetと同様の集合演算を行うことができます。 それぞれ対応するメソッドは次のとおりです。

HashSet/SortedSetの集合演算メソッドと、対応するLINQの拡張メソッド
演算 HashSet/SortedSetのメソッド LINQの拡張メソッド
積集合 IntersectWith Intersect
和集合 UnionWith Union
差集合 ExceptWith Except
対象差 SymmetricExceptWith -

以下の例はこれらのメソッドを使ってHashSet・SortedSetと同様の集合演算を行う例です。 LINQでは、SymmetricExceptWithに相当するような対象差を求めるメソッドは直接提供されませんが、以下の例のように差集合同士の和集合を求めることで対象差を求めることができます。

LINQの拡張メソッドを使って配列同士の積集合・和集合・差集合・対象差を求める
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    var set1 = new int[] {6, 2, 0, 4, 8};
    var set2 = new int[] {3, 1, 2, 0, 4};

    // 積集合を求める
    Console.WriteLine(string.Join(", ", set1.Intersect(set2)));

    // 和集合を求める
    Console.WriteLine(string.Join(", ", set1.Union(set2)));

    // 差集合を求める
    Console.WriteLine(string.Join(", ", set1.Except(set2)));

    // 対象差を求める
    var diffset1 = set1.Except(set2);
    var diffset2 = set2.Except(set1);

    Console.WriteLine(string.Join(", ", diffset1.Union(diffset2)));
  }
}
LINQの拡張メソッドを使って配列同士の積集合・和集合・差集合・対象差を求める
Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim set1() As Integer = New Integer() {6, 2, 0, 4, 8}
    Dim set2() As Integer = New Integer() {3, 1, 2, 0, 4}

    ' 積集合を求める
    Console.WriteLine(String.Join(", ", set1.Intersect(set2)))

    ' 和集合を求める
    Console.WriteLine(String.Join(", ", set1.Union(set2)))

    ' 差集合を求める
    Console.WriteLine(String.Join(", ", set1.Except(set2)))

    ' 対象差を求める
    Dim diffset1 As IEnumerable(Of Integer) = set1.Except(set2)
    Dim diffset2 As IEnumerable(Of Integer) = set2.Except(set1)

    Console.WriteLine(String.Join(", ", diffset1.Union(diffset2)))
  End Sub
End Class
実行結果
2, 0, 4, 
6, 2, 0, 4, 8, 3, 1, 
6, 8, 
6, 8, 3, 1, 

包含関係の検証

HashSetクラス・SortedSetクラスには包含関係の検証を行うメソッドとして次のメソッドが用意されています。 いずれも引数としてIEnumerable<T>を取り、戻り値は真偽値(boolean)です。 なお、要素の並びが異なっていても包含関係には影響しません。 これらのメソッドでは順序にかかわらずどのような要素が含まれているかどうかのみが検証されます。

包含関係の検証を行うメソッド
メソッド 動作
HashSet.SetEquals
SortedSet.SetEquals
現在の集合と引数で指定されたIEnumerable<T>が等しいかどうか調べる
(現在の集合と引数の集合がすべて同じ要素で構成されている場合はtrue)
HashSet.Overlaps
SortedSet.Overlaps
現在の集合と引数で指定されたIEnumerable<T>が共通する要素を持つかどうか調べる
(現在の集合と引数の集合が一つでも同じ要素を持つ場合はtrue)
HashSet.IsSubsetOf
SortedSet.IsSubsetOf
現在の集合が引数で指定されたIEnumerable<T>の部分集合かどうか調べる
(現在の集合が引数の集合に含まれる場合はtrue)
HashSet.IsProperSubsetOf
SortedSet.IsProperSubsetOf
現在の集合が引数で指定されたIEnumerable<T>の真部分集合かどうか調べる
(現在の集合が引数の集合に含まれ、かつ両者が等しくない場合はtrue)
HashSet.IsSupersetOf
SortedSet.IsSupersetOf
現在の集合が引数で指定されたIEnumerable<T>の上位集合かどうか調べる
(現在の集合が引数の集合を含む場合はtrue)
HashSet.IsProperSupersetOf
SortedSet.IsProperSupersetOf
現在の集合が引数で指定されたIEnumerable<T>の真上位集合かどうか調べる
(現在の集合が引数の集合を含み、かつ両者が等しくない場合はtrue)

以下は、上記のメソッドを使った例です。 SortedSetでは並べ替えが行われる以外は、結果は同じです。

HashSet
SetEquals・Overrapsメソッドを使ってHashSetが別の集合と完全一致・部分一致するか調べる
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new HashSet<int>() {3, 4, 1, 5, 2};

    Console.WriteLine("Set: {{{0}}}", string.Join(", ", s));
    Console.WriteLine();

    // 2つの集合が完全に一致するか調べる (SetEquals)
    Console.WriteLine(
      "SetEquals {{1, 2, 3, 4, 5}}: {0}",
      s.SetEquals(new[] {1, 2, 3, 4, 5})
    );
    Console.WriteLine(
      "SetEquals {{1, 2, 3, 4, 6}}: {0}",
      s.SetEquals(new[] {1, 2, 3, 4, 6})
    );

    //s 2つの集合が部分一致するか調べる (Overraps)
    Console.WriteLine(
      "Overraps {{2, 3, 5, 7}}: {0}",
      s.Overlaps(new[] {2, 3, 5, 7})
    );
    Console.WriteLine(
      "Overraps {{0, 6}}: {0}",
      s.Overlaps(new[] {0, 6})
    );
  }
}
SetEquals・Overrapsメソッドを使ってHashSetが別の集合と完全一致・部分一致するか調べる
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New HashSet(Of Integer)() From {3, 4, 1, 5, 2}

    Console.WriteLine("Set: {{{0}}}", String.Join(", ", s))
    Console.WriteLine()

    ' 2つの集合が完全に一致するか調べる (SetEquals)
    Console.WriteLine(
      "SetEquals {{1, 2, 3, 4, 5}}: {0}",
      s.SetEquals(New Integer() {1, 2, 3, 4, 5})
    )
    Console.WriteLine(
      "SetEquals {{1, 2, 3, 4, 6}}: {0}",
      s.SetEquals(New Integer() {1, 2, 3, 4, 6})
    )

    ' 2つの集合が部分一致するか調べる (Overraps)
    Console.WriteLine(
      "Overraps {{2, 3, 5, 7}}: {0}",
      s.Overlaps(New Integer() {2, 3, 5, 7})
    )
    Console.WriteLine(
      "Overraps {{0, 6}}: {0}",
      s.Overlaps(New Integer() {0, 6})
    )
  End Sub
End Class
実行結果
Set: {3, 4, 1, 5, 2}

SetEquals {1, 2, 3, 4, 5}: True
SetEquals {1, 2, 3, 4, 6}: False
Overraps {2, 3, 5, 7}: True
Overraps {0, 6}: False
SortedSet
SetEquals・Overrapsメソッドを使ってSortedSetが別の集合と完全一致・部分一致するか調べる
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<int>() {3, 4, 1, 5, 2};

    Console.WriteLine("Set: {{{0}}}", string.Join(", ", s));
    Console.WriteLine();

    // 2つの集合が完全に一致するか調べる (SetEquals)
    Console.WriteLine(
      "SetEquals {{1, 2, 3, 4, 5}}: {0}",
      s.SetEquals(new[] {1, 2, 3, 4, 5})
    );
    Console.WriteLine(
      "SetEquals {{1, 2, 3, 4, 6}}: {0}",
      s.SetEquals(new[] {1, 2, 3, 4, 6})
    );

    //s 2つの集合が部分一致するか調べる (Overraps)
    Console.WriteLine(
      "Overraps {{2, 3, 5, 7}}: {0}",
      s.Overlaps(new[] {2, 3, 5, 7})
    );
    Console.WriteLine(
      "Overraps {{0, 6}}: {0}",
      s.Overlaps(new[] {0, 6})
    );
  }
}
SetEquals・Overrapsメソッドを使ってSortedSetが別の集合と完全一致・部分一致するか調べる
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of Integer)() From {3, 4, 1, 5, 2}

    Console.WriteLine("Set: {{{0}}}", String.Join(", ", s))
    Console.WriteLine()

    ' 2つの集合が完全に一致するか調べる (SetEquals)
    Console.WriteLine(
      "SetEquals {{1, 2, 3, 4, 5}}: {0}",
      s.SetEquals(New Integer() {1, 2, 3, 4, 5})
    )
    Console.WriteLine(
      "SetEquals {{1, 2, 3, 4, 6}}: {0}",
      s.SetEquals(New Integer() {1, 2, 3, 4, 6})
    )

    ' 2つの集合が部分一致するか調べる (Overraps)
    Console.WriteLine(
      "Overraps {{2, 3, 5, 7}}: {0}",
      s.Overlaps(New Integer() {2, 3, 5, 7})
    )
    Console.WriteLine(
      "Overraps {{0, 6}}: {0}",
      s.Overlaps(New Integer() {0, 6})
    )
  End Sub
End Class
実行結果
Set: {1, 2, 3, 4, 5}

SetEquals {1, 2, 3, 4, 5}: True
SetEquals {1, 2, 3, 4, 6}: False
Overraps {2, 3, 5, 7}: True
Overraps {0, 6}: False
HashSet
IsSubsetOf・IsProperSubsetOfメソッドを使ってHashSetが別の集合の部分集合か・真部分集合かどうか調べる
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new HashSet<int>() {3, 4, 1, 5, 2};

    Console.WriteLine("Set: {{{0}}}", string.Join(", ", s));
    Console.WriteLine();

    // HashSetが引数の集合の部分集合かどうか調べる (IsSubsetOf)
    Console.WriteLine(
      "IsSubsetOf       {{0, 1, 2, 3, 4, 5, 6}}: {0}",
      s.IsSubsetOf(new[] {0, 1, 2, 3, 4, 5, 6})
    );
    // HashSetが引数の集合の真部分集合かどうか調べる (IsProperSubsetOf)
    Console.WriteLine(
      "IsProperSubsetOf {{0, 1, 2, 3, 4, 5, 6}}: {0}",
      s.IsProperSubsetOf(new[] {0, 1, 2, 3, 4, 5, 6})
    );

    Console.WriteLine(
      "IsSubsetOf       {{1, 2, 3, 4, 5}}: {0}",
      s.IsSubsetOf(new[] {1, 2, 3, 4, 5})
    );
    Console.WriteLine(
      "IsProperSubsetOf {{1, 2, 3, 4, 5}}: {0}",
      s.IsProperSubsetOf(new[] {1, 2, 3, 4, 5})
    );
  }
}
IsSubsetOf・IsProperSubsetOfメソッドを使ってHashSetが別の集合の部分集合か・真部分集合かどうか調べる
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New HashSet(Of Integer)() From {3, 4, 1, 5, 2}

    Console.WriteLine("Set: {{{0}}}", String.Join(", ", s))
    Console.WriteLine()

    ' HashSetが引数の集合の部分集合かどうか調べる (IsSubsetOf)
    Console.WriteLine(
      "IsSubsetOf       {{0, 1, 2, 3, 4, 5, 6}}: {0}",
      s.IsSubsetOf(New Integer() {0, 1, 2, 3, 4, 5, 6})
    )
    ' HashSetが引数の集合の真部分集合かどうか調べる (IsProperSubsetOf)
    Console.WriteLine(
      "IsProperSubsetOf {{0, 1, 2, 3, 4, 5, 6}}: {0}",
      s.IsProperSubsetOf(New Integer() {0, 1, 2, 3, 4, 5, 6})
    )

    Console.WriteLine(
      "IsSubsetOf       {{1, 2, 3, 4, 5}}: {0}",
      s.IsSubsetOf(New Integer() {1, 2, 3, 4, 5})
    )
    Console.WriteLine(
      "IsProperSubsetOf {{1, 2, 3, 4, 5}}: {0}",
      s.IsProperSubsetOf(New Integer() {1, 2, 3, 4, 5})
    )
  End Sub
End Class
実行結果
Set: {3, 4, 1, 5, 2}

IsSubsetOf       {0, 1, 2, 3, 4, 5, 6}: True
IsProperSubsetOf {0, 1, 2, 3, 4, 5, 6}: True
IsSubsetOf       {1, 2, 3, 4, 5}: True
IsProperSubsetOf {1, 2, 3, 4, 5}: False
SortedSet
IsSubsetOf・IsProperSubsetOfメソッドを使ってSortedSetが別の集合の部分集合か・真部分集合かどうか調べる
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<int>() {3, 4, 1, 5, 2};

    Console.WriteLine("Set: {{{0}}}", string.Join(", ", s));
    Console.WriteLine();

    // SortedSetが引数の集合の部分集合かどうか調べる (IsSubsetOf)
    Console.WriteLine(
      "IsSubsetOf       {{0, 1, 2, 3, 4, 5, 6}}: {0}",
      s.IsSubsetOf(new[] {0, 1, 2, 3, 4, 5, 6})
    );
    // SortedSetが引数の集合の真部分集合かどうか調べる (IsProperSubsetOf)
    Console.WriteLine(
      "IsProperSubsetOf {{0, 1, 2, 3, 4, 5, 6}}: {0}",
      s.IsProperSubsetOf(new[] {0, 1, 2, 3, 4, 5, 6})
    );

    Console.WriteLine(
      "IsSubsetOf       {{1, 2, 3, 4, 5}}: {0}",
      s.IsSubsetOf(new[] {1, 2, 3, 4, 5})
    );
    Console.WriteLine(
      "IsProperSubsetOf {{1, 2, 3, 4, 5}}: {0}",
      s.IsProperSubsetOf(new[] {1, 2, 3, 4, 5})
    );
  }
}
IsSubsetOf・IsProperSubsetOfメソッドを使ってSortedSetが別の集合の部分集合か・真部分集合かどうか調べる
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of Integer)() From {3, 4, 1, 5, 2}

    Console.WriteLine("Set: {{{0}}}", String.Join(", ", s))
    Console.WriteLine()

    ' SortedSetが引数の集合の部分集合かどうか調べる (IsSubsetOf)
    Console.WriteLine(
      "IsSubsetOf       {{0, 1, 2, 3, 4, 5, 6}}: {0}",
      s.IsSubsetOf(New Integer() {0, 1, 2, 3, 4, 5, 6})
    )
    ' SortedSetが引数の集合の真部分集合かどうか調べる (IsProperSubsetOf)
    Console.WriteLine(
      "IsProperSubsetOf {{0, 1, 2, 3, 4, 5, 6}}: {0}",
      s.IsProperSubsetOf(New Integer() {0, 1, 2, 3, 4, 5, 6})
    )

    Console.WriteLine(
      "IsSubsetOf       {{1, 2, 3, 4, 5}}: {0}",
      s.IsSubsetOf(New Integer() {1, 2, 3, 4, 5})
    )
    Console.WriteLine(
      "IsProperSubsetOf {{1, 2, 3, 4, 5}}: {0}",
      s.IsProperSubsetOf(New Integer() {1, 2, 3, 4, 5})
    )
  End Sub
End Class
実行結果
Set: {1, 2, 3, 4, 5}

IsSubsetOf       {0, 1, 2, 3, 4, 5, 6}: True
IsProperSubsetOf {0, 1, 2, 3, 4, 5, 6}: True
IsSubsetOf       {1, 2, 3, 4, 5}: True
IsProperSubsetOf {1, 2, 3, 4, 5}: False
HashSet
IsSupersetOf・IsProperSupersetOfメソッドを使ってHashSetが別の集合の上位集合か・真上位集合かどうか調べる
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new HashSet<int>() {3, 4, 1, 5, 2};

    Console.WriteLine("Set: {{{0}}}", string.Join(", ", s));
    Console.WriteLine();

    // HashSetが引数の集合の上位集合かどうか調べる (IsSupersetOf)
    Console.WriteLine(
      "IsSupersetOf       {{2, 3, 4}}: {0}",
      s.IsSupersetOf(new[] {2, 3, 4})
    );
    // HashSetが引数の集合の真上位集合かどうか調べる (IsProperSupersetOf)
    Console.WriteLine(
      "IsProperSupersetOf {{2, 3, 4}}: {0}",
      s.IsProperSupersetOf(new[] {2, 3, 4})
    );
    Console.WriteLine(
      "IsSupersetOf       {{1, 2, 3, 4, 5}}: {0}",
      s.IsSupersetOf(new[] {1, 2, 3, 4, 5})
    );
    Console.WriteLine(
      "IsProperSupersetOf {{1, 2, 3, 4, 5}}: {0}",
      s.IsProperSupersetOf(new[] {1, 2, 3, 4, 5})
    );
  }
}
IsSupersetOf・IsProperSupersetOfメソッドを使ってHashSetが別の集合の上位集合か・真上位集合かどうか調べる
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New HashSet(Of Integer)() From {3, 4, 1, 5, 2}

    Console.WriteLine("Set: {{{0}}}", String.Join(", ", s))
    Console.WriteLine()

    ' HashSetが引数の集合の上位集合かどうか調べる (IsSupersetOf)
    Console.WriteLine(
      "IsSupersetOf       {{2, 3, 4}}: {0}",
      s.IsSupersetOf(New Integer() {2, 3, 4})
    )
    ' HashSetが引数の集合の真上位集合かどうか調べる (IsProperSupersetOf)
    Console.WriteLine(
      "IsProperSupersetOf {{2, 3, 4}}: {0}",
      s.IsProperSupersetOf(New Integer() {2, 3, 4})
    )
    Console.WriteLine(
      "IsSupersetOf       {{1, 2, 3, 4, 5}}: {0}",
      s.IsSupersetOf(New Integer() {1, 2, 3, 4, 5})
    )
    Console.WriteLine(
      "IsProperSupersetOf {{1, 2, 3, 4, 5}}: {0}",
      s.IsProperSupersetOf(New Integer() {1, 2, 3, 4, 5})
    )
  End Sub
End Class
実行結果
Set: {3, 4, 1, 5, 2}

IsSupersetOf       {2, 3, 4}: True
IsProperSupersetOf {2, 3, 4}: True
IsSupersetOf       {1, 2, 3, 4, 5}: True
IsProperSupersetOf {1, 2, 3, 4, 5}: False
SortedSet
IsSupersetOf・IsProperSupersetOfメソッドを使ってSortedSetが別の集合の上位集合か・真上位集合かどうか調べる
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<int>() {3, 4, 1, 5, 2};

    Console.WriteLine("Set: {{{0}}}", string.Join(", ", s));
    Console.WriteLine();

    // SortedSetが引数の集合の上位集合かどうか調べる (IsSupersetOf)
    Console.WriteLine(
      "IsSupersetOf       {{2, 3, 4}}: {0}",
      s.IsSupersetOf(new[] {2, 3, 4})
    );
    // SortedSetが引数の集合の真上位集合かどうか調べる (IsProperSupersetOf)
    Console.WriteLine(
      "IsProperSupersetOf {{2, 3, 4}}: {0}",
      s.IsProperSupersetOf(new[] {2, 3, 4})
    );
    Console.WriteLine(
      "IsSupersetOf       {{1, 2, 3, 4, 5}}: {0}",
      s.IsSupersetOf(new[] {1, 2, 3, 4, 5})
    );
    Console.WriteLine(
      "IsProperSupersetOf {{1, 2, 3, 4, 5}}: {0}",
      s.IsProperSupersetOf(new[] {1, 2, 3, 4, 5})
    );
  }
}
IsSupersetOf・IsProperSupersetOfメソッドを使ってSortedSetが別の集合の上位集合か・真上位集合かどうか調べる
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of Integer)() From {3, 4, 1, 5, 2}

    Console.WriteLine("Set: {{{0}}}", String.Join(", ", s))
    Console.WriteLine()

    ' SortedSetが引数の集合の上位集合かどうか調べる (IsSupersetOf)
    Console.WriteLine(
      "IsSupersetOf       {{2, 3, 4}}: {0}",
      s.IsSupersetOf(New Integer() {2, 3, 4})
    )
    ' SortedSetが引数の集合の真上位集合かどうか調べる (IsProperSupersetOf)
    Console.WriteLine(
      "IsProperSupersetOf {{2, 3, 4}}: {0}",
      s.IsProperSupersetOf(New Integer() {2, 3, 4})
    )
    Console.WriteLine(
      "IsSupersetOf       {{1, 2, 3, 4, 5}}: {0}",
      s.IsSupersetOf(New Integer() {1, 2, 3, 4, 5})
    )
    Console.WriteLine(
      "IsProperSupersetOf {{1, 2, 3, 4, 5}}: {0}",
      s.IsProperSupersetOf(New Integer() {1, 2, 3, 4, 5})
    )
  End Sub
End Class
実行結果
Set: {1, 2, 3, 4, 5}

IsSupersetOf       {2, 3, 4}: True
IsProperSupersetOf {2, 3, 4}: True
IsSupersetOf       {1, 2, 3, 4, 5}: True
IsProperSupersetOf {1, 2, 3, 4, 5}: False

HashSetでの等価性比較のカスタマイズ

HashSetのコンストラクタで適切なIEqualityComparer<T>インターフェイスを指定することで、要素の等価性比較時の動作をカスタマイズ出来ます。 例えば、比較の際に大文字小文字の違いを無視するようにするといったことが出来ます。 以下は、StringComparerクラスを使って、大文字小文字の違いを無視するHashSetを作成する例です。

StringComparerを指定して大文字小文字の違いを意識する/無視するHashSetを作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var names = new[] {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // 大文字小文字の違いを意識するHashSet
    var caseSensitiveSet = new HashSet<string>(names, StringComparer.Ordinal);
    // 大文字小文字の違いを無視するHashSet
    var caseInsensitiveSet = new HashSet<string>(names, StringComparer.OrdinalIgnoreCase);

    Console.WriteLine(caseSensitiveSet.IsSupersetOf(new[] {"aLiCe", "BOB", "dave"}));
    Console.WriteLine(caseInsensitiveSet.IsSupersetOf(new[] {"aLiCe", "BOB", "dave"}));
  }
}
StringComparerを指定して大文字小文字の違いを意識する/無視するHashSetを作成する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim names() As String = New String() {"Alice", "Bob", "Charlie", "Dave", "Eve"}

    ' 大文字小文字の違いを意識するHashSet
    Dim caseSensitiveSet As New HashSet(Of String)(names, StringComparer.Ordinal)
    ' 大文字小文字の違いを無視するHashSet
    Dim caseInsensitiveSet As New HashSet(Of String)(names, StringComparer.OrdinalIgnoreCase)

    Console.WriteLine(caseSensitiveSet.IsSupersetOf(New String() {"aLiCe", "BOB", "dave"}))
    Console.WriteLine(caseInsensitiveSet.IsSupersetOf(New String() {"aLiCe", "BOB", "dave"}))
  End Sub
End Class
実行結果
False
True

IEqualityComparer<T>インターフェイスや実装例については等価性の定義と比較、StringComparerについては文字列と比較オプション・カルチャの並べ替え規則 §.StringComparison列挙型とStringComparerクラスを参照してください。

SortedSetでは等価性比較だけではなく大小関係の比較も行うため、IEqualityComparer<T>インターフェイスではなくIComparer<T>インターフェイスを要求します。

HashSetでKeyValuePairを扱う

等価性比較のカスタマイズ例として、HashSetでKeyValuePairを扱う例をみてみます。 次の例では、KeyValuePairからKeyの値を取得して比較を行うクラスKeyValuePairEqualityComparerを作成し、それをHashSetのコンストラクタに渡しています。 これによりKeyValuePair同士の比較ができるようになり、HashSetでKeyValuePairを扱うことができるようになります。

HashSetでKeyValuePairを扱う
using System;
using System.Collections.Generic;

// KeyValuePair<string, int>の等価性比較を行うIEqualityComparer<T>
class KeyValuePairEqualityComparer : EqualityComparer<KeyValuePair<string, int>> {
  public override bool Equals(KeyValuePair<string, int> x, KeyValuePair<string, int> y)
    => string.Equals(x.Key, y.Key); // KeyValuePairのKeyを比較する

  public override int GetHashCode(KeyValuePair<string, int> obj)
    => obj.Key.GetHashCode(); // KeyValuePairのKeyからハッシュ値を取得する
}

class Sample {
  static void Main()
  {
    var comparer = new KeyValuePairEqualityComparer();
    var s1 = new HashSet<KeyValuePair<string, int>>(comparer);

    s1.Add(KeyValuePair.Create("Dave", 1));
    s1.Add(KeyValuePair.Create("Alice", 2));
    s1.Add(KeyValuePair.Create("Alice", 99999));
    s1.Add(KeyValuePair.Create("Bob", 3));
    s1.Add(KeyValuePair.Create("Eve", 4));
    s1.Add(KeyValuePair.Create("Charlie", 5));

    Console.WriteLine(string.Join(", ", s1));

    var s2 = new HashSet<KeyValuePair<string, int>>(comparer);

    s2.Add(KeyValuePair.Create("Alice", 3));
    s2.Add(KeyValuePair.Create("Bob", 1));
    s2.Add(KeyValuePair.Create("Charlie", 2));

    Console.WriteLine(string.Join(", ", s2));

    // 差集合を求める
    Console.WriteLine("[ExceptWith]");

    s1.ExceptWith(s2);

    Console.WriteLine(string.Join(", ", s1));
  }
}
HashSetでKeyValuePairを扱う
Imports System
Imports System.Collections.Generic

' KeyValuePair(Of String, Integer)の等価性比較を行うIEqualityComparer(Of T)
Class KeyValueEqualityComparer
  Inherits EqualityComparer(Of KeyValuePair(Of String, Integer))

  Public Overrides Function Equals(ByVal x As KeyValuePair(Of String, Integer), ByVal y As KeyValuePair(Of String, Integer)) As Boolean
    ' KeyValuePairのKeyを比較する
    Return String.Equals(x.Key, y.Key)
  End Function

  Public Overrides Function GetHashCode(ByVal obj As KeyValuePair(Of String, Integer)) As Integer
    ' KeyValuePairのKeyからハッシュ値を取得する
    Return obj.Key.GetHashCode()
  End Function
End Class

Class Sample
  Shared Sub Main()
    Dim comparer As New KeyValueEqualityComparer()
    Dim s1 As New HashSet(Of KeyValuePair(Of String, Integer))(comparer)

    s1.Add(New KeyValuePair(Of String, Integer)("Dave", 1))
    s1.Add(New KeyValuePair(Of String, Integer)("Alice", 2))
    s1.Add(New KeyValuePair(Of String, Integer)("Alice", 99999))
    s1.Add(New KeyValuePair(Of String, Integer)("Bob", 3))
    s1.Add(New KeyValuePair(Of String, Integer)("Eve", 4))
    s1.Add(New KeyValuePair(Of String, Integer)("Charlie", 5))

    Console.WriteLine(String.Join(", ", s1))

    Dim s2 As New HashSet(Of KeyValuePair(Of String, Integer))(comparer)

    s2.Add(New KeyValuePair(Of String, Integer)("Alice", 3))
    s2.Add(New KeyValuePair(Of String, Integer)("Bob", 1))
    s2.Add(New KeyValuePair(Of String, Integer)("Charlie", 2))

    Console.WriteLine(String.Join(", ", s2))

    ' 差集合を求める
    Console.WriteLine("[ExceptWith]")

    s1.ExceptWith(s2)

    Console.WriteLine(String.Join(", ", s1))
  End Sub
End Class
実行結果
[Dave, 1], [Alice, 2], [Bob, 3], [Eve, 4], [Charlie, 5]
[Alice, 3], [Bob, 1], [Charlie, 2]
[ExceptWith]
[Dave, 1], [Eve, 4]

この例のKeyValuePairEqualityComparerではKeyの値のみを比較しているため、Valueにどのような値が格納されているかといったことは一切考慮されません。 Keyの値が同一であれば、Valueの値に関わらず同一の要素とみなされます。

SortedSetでの大小比較のカスタマイズ

SortedSetのコンストラクタで適切なIComparer<T>インターフェイスを指定することで、要素の大小関係比較時の動作をカスタマイズ出来ます。 例えば、比較の際に大文字小文字の違いを無視するようにするといったことが出来ます。

以下は、大文字小文字を無視し、アルファベット順とは逆順(Z-Aの順)になるようにソートするIComparer<string>を実装し、SortedSetでの並べ替え順をカスタマイズする例です。

大文字小文字の違いを無視し、アルファベット順とは逆順に並べ替えるSortedSetを作成する
using System;
using System.Collections.Generic;

// 大文字小文字の違いを無視し、アルファベット順とは逆順にソートするためのIComparer
class CaseInsensitiveReverseStringComparer : IComparer<string> {
  public int Compare(string x, string y)
    // StringComparer.OrdinalIgnoreCase.Compareとは逆の結果を返すようにする
    => -1 * StringComparer.OrdinalIgnoreCase.Compare(x, y);
}

class Sample {
  static void Main()
  {
    var names = new[] {"Alice", "Eve", "Charlie", "Bob", "Dave"};

    var caseSensitiveSet = new SortedSet<string>(names, StringComparer.Ordinal);
    var reverseCaseInsensitiveSet = new SortedSet<string>(names, new CaseInsensitiveReverseStringComparer());

    Console.WriteLine("caseSensitiveSet");
    Console.WriteLine(string.Join(", ", caseSensitiveSet));
    Console.WriteLine(caseSensitiveSet.IsSupersetOf(new[] {"aLiCe", "BOB", "dave"}));
    Console.WriteLine();

    Console.WriteLine("reverseCaseInsensitiveSet");
    Console.WriteLine(string.Join(", ", reverseCaseInsensitiveSet));
    Console.WriteLine(reverseCaseInsensitiveSet.IsSupersetOf(new[] {"aLiCe", "BOB", "dave"}));
  }
}
大文字小文字の違いを無視し、アルファベット順とは逆順に並べ替えるSortedSetを作成する
Imports System
Imports System.Collections.Generic

' 大文字小文字の違いを無視し、アルファベット順とは逆順にソートするためのIComparer
Class CaseInsensitiveReverseStringComparer
  Implements IComparer(Of String)

  Public Function Compare(ByVal x As String, ByVal y As String) As Integer Implements IComparer(Of String).Compare
    ' StringComparer.OrdinalIgnoreCase.Compareとは逆の結果を返すようにする
    Return -1 * StringComparer.OrdinalIgnoreCase.Compare(x, y)
  End Function
End Class

Class Sample
  Shared Sub Main()
    Dim names() As String = New String() {"Alice", "Bob", "Charlie", "Dave", "Eve"}

    Dim caseSensitiveSet As New SortedSet(Of String)(names, StringComparer.Ordinal)
    Dim reverseCaseInsensitiveSet As New SortedSet(Of String)(names, New CaseInsensitiveReverseStringComparer())

    Console.WriteLine("caseSensitiveSet")
    Console.WriteLine(String.Join(", ", caseSensitiveSet))
    Console.WriteLine(caseSensitiveSet.IsSupersetOf(New String() {"aLiCe", "BOB", "dave"}))
    Console.WriteLine()

    Console.WriteLine("reverseCaseInsensitiveSet")
    Console.WriteLine(String.Join(", ", reverseCaseInsensitiveSet))
    Console.WriteLine(reverseCaseInsensitiveSet.IsSupersetOf(New String() {"aLiCe", "BOB", "dave"}))
  End Sub
End Class
実行結果
caseSensitiveSet
Alice, Bob, Charlie, Dave, Eve
False

reverseCaseInsensitiveSet
Eve, Dave, Charlie, Bob, Alice
True

IComparer<T>インターフェイスや実装例については大小関係の定義と比較を参照してください。

文字列の大小関係、StringComparerについて詳しくは文字列と比較オプション・カルチャの並べ替え規則を参照してください。

SortedSetのみで提供される操作

以下の操作はHashSetには用意されておらず、SortedSetのみで提供される操作です。

最小値・最大値

SortedSetクラスでは、MinプロパティおよびMaxプロパティを参照することで、SortedSetの並べ替え順序での最小・最大の要素を取得することが出来ます。

Min・Maxプロパティを参照してSortedSet内の最小・最大の要素を取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<string>() {"Alice", "Eve", "Charlie", "Bob", "Dave"};

    Console.WriteLine(string.Join(", ", s));

    // 最小値と最大値を表示
    Console.WriteLine($"Mix: {s.Min}");
    Console.WriteLine($"Max: {s.Max}");
  }
}
Min・Maxプロパティを参照してSortedSet内の最小・最大の要素を取得する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of String)() From {"Alice", "Eve", "Charlie", "Bob", "Dave"}

    Console.WriteLine(String.Join(", ", s))

    ' 最小値と最大値を表示
    Console.WriteLine($"Mix: {s.Min}")
    Console.WriteLine($"Max: {s.Max}")
  End Sub
End Class
実行結果
Set: Alice, Bob, Charlie, Dave, Eve,
Mix: Alice
Max: Eve

SortedSetのコンストラクタでIComparer<T>を明示的に指定しない場合、最小値・最大値はデフォルトのソート順(大小関係)に従って求められます。 一方、コンストラクタでIComparer<T>を指定した場合は、そのIComparer<T>で定義される大小関係に従って最小値・最大値が求められることになります。

つまり、Minプロパティ・Maxプロパティは、IComparer<T>で定義されるソート順でSortedSetをソート(あるいは列挙)したときの、最初または最後の要素を取得するプロパティとなります。 このため、Minプロパティ・Maxプロパティで取得できる値が、常に値としての最小値・最大値になるとは限りません。

デフォルトとは逆順に並べ替えるIComparer<T>を指定してSortedSetを作成し、Minプロパティ・Maxプロパティが返す値の違いを見ると次のようになります。

Min・Maxプロパティを参照してSortedSetの並べ替え順での最初・最後の要素を取得する
using System;
using System.Collections.Generic;

class ReverseIntComparer : IComparer<int> {
  public int Compare(int x, int y)
    // intのデフォルトの大小関係とは逆の結果を返すようにする
    => -1 * Comparer<int>.Default.Compare(x, y);
}

class Sample {
  static void Main()
  {
    // デフォルトの順序で並べ替えを行うSortedSet
    var s1 = new SortedSet<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(string.Join(", ", s1));

    // 最小値と最大値を表示
    // (デフォルトのソート順での最初と最後の要素を表示する)
    Console.WriteLine($"Mix: {s1.Min}");
    Console.WriteLine($"Max: {s1.Max}");
    Console.WriteLine();

    // デフォルトとは逆の順序で並べ替えを行うSortedSet
    var s2 = new SortedSet<int>(new ReverseIntComparer()) {0, 1, 2, 3, 4};

    Console.WriteLine(string.Join(", ", s2));

    // 最小値と最大値を表示
    // (デフォルトとは逆のソート順での最初と最後の要素を表示する)
    Console.WriteLine($"Mix: {s2.Min}");
    Console.WriteLine($"Max: {s2.Max}");
  }
}
Min・Maxプロパティを参照してSortedSetの並べ替え順での最初・最後の要素を取得する
Imports System
Imports System.Collections.Generic

Class ReverseIntegerComparer
  Implements IComparer(Of Integer)

  Public Function Compare(ByVal x As Integer, ByVal y As Integer) As Integer Implements IComparer(Of Integer).Compare
    ' Integerのデフォルトの大小関係とは逆の結果を返すようにする
    Return -1 * Comparer(Of Integer).Default.Compare(x, y)
  End Function
End Class

Class Sample
  Shared Sub Main()
    ' デフォルトの順序で並べ替えを行うSortedSet
    Dim s1 As New SortedSet(Of Integer)() From {0, 1, 2, 3, 4}

    Console.WriteLine(String.Join(", ", s1))

    ' 最小値と最大値を表示
    ' (デフォルトのソート順での最初と最後の要素を表示する)
    Console.WriteLine($"Mix: {s1.Min}")
    Console.WriteLine($"Max: {s1.Max}")
    Console.WriteLine()

    ' デフォルトとは逆の順序で並べ替えを行うSortedSet
    Dim s2 As New SortedSet(Of Integer)(New ReverseIntegerComparer()) From {0, 1, 2, 3, 4}

    Console.WriteLine(String.Join(", ", s2))

    ' 最小値と最大値を表示
    ' (デフォルトとは逆のソート順での最初と最後の要素を表示する)
    Console.WriteLine($"Mix: {s2.Min}")
    Console.WriteLine($"Max: {s2.Max}")
  End Sub
End Class
実行結果
0, 1, 2, 3, 4
Mix: 0
Max: 4

4, 3, 2, 1, 0
Mix: 4
Max: 0

HashSetでの最小値・最大値の取得

SortedSetと異なり、HashSetからは直接最小値・最大値を取得することはできませんが、LINQの拡張メソッドであるMinメソッドMaxメソッドを使うことで求めることができます。

LINQのMin・Maxメソッドを使ってHashSet内の最小値・最大値を取得する
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    var s = new HashSet<string>() {"Alice", "Eve", "Charlie", "Bob", "Dave"};

    // 最小値と最大値を表示
    Console.WriteLine($"Mix: {s.Min()}");
    Console.WriteLine($"Max: {s.Max()}");
  }
}
LINQのMin・Maxメソッドを使ってHashSet内の最小値・最大値を取得する
Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim s As New HashSet(Of String) From {"Alice", "Eve", "Charlie", "Bob", "Dave"}

    ' 最小値と最大値を表示
    Console.WriteLine($"Mix: {s.Min()}")
    Console.WriteLine($"Max: {s.Max()}")
  End Sub
End Class
実行結果
Mix: Alice
Max: Eve

LINQのMinメソッド・Maxメソッドでは全要素を走査した上で最大・最小の要素を返します。 このメソッドをSortedSetに対しても用いることはできますが、無駄が多く、SortedListのMinプロパティ・Maxプロパティの代わりとしてMinメソッド・Maxメソッドを用いる利点はありません。

逆順での列挙

Reverseメソッドは、SortedSetを通常とは逆順に列挙する列挙子(IEnumerator<T>)を返します。 つまり、Reverseメソッドを使うと、SortedSet内の要素を逆の順序で列挙することができます。

Reverseメソッドは、Array.ReverseメソッドList.Reverseメソッドとは異なり、SortedSet内の要素の並びを変更しない、非破壊的なメソッドです。 Reverseメソッドを呼び出してもSortedSet内の要素は逆順にはならず、あくまで逆順で列挙する列挙子を返すだけという点に注意してください。

Reverseメソッドを使ってSortedSet内の要素を逆順で列挙する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<int>() {5, 1, 0, 3, 4, 2};

    // デフォルトの順序でSortedSetを列挙
    foreach (var e in s) {
      Console.Write("{0}, ", e);
    }
    Console.WriteLine();

    // デフォルトとは逆順でSortedSetを列挙
    foreach (var e in s.Reverse()) {
      Console.Write("{0}, ", e);
    }
    Console.WriteLine();
  }
}
Reverseメソッドを使ってSortedSet内の要素を逆順で列挙する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of Integer) From {5, 1, 0, 3, 4, 2}

    ' デフォルトの順序でSortedSetを列挙
    For Each e As Integer In s
      Console.Write("{0}, ", e)
    Next
    Console.WriteLine()

    ' デフォルトとは逆順でSortedSetを列挙
    For Each e As Integer In s.Reverse()
      Console.Write("{0}, ", e)
    Next
    Console.WriteLine()
  End Sub
End Class
実行結果
5, 4, 3, 2, 1, 0, 
0, 1, 2, 3, 4, 5, 

Reverseメソッドは列挙だけでなく、IEnumerator<T>を引数にとるメソッドやLINQのメソッドに渡して使うこともできます。

Reverseメソッドを使ってSortedSet内の要素を逆順で文字列として結合する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<int>() {5, 1, 0, 3, 4, 2};

    // デフォルトの順序でSortedSet内の要素を文字列として結合する
    Console.WriteLine(string.Join(", ", s));

    // デフォルトとは逆順でSortedSet内の要素を文字列として結合する
    Console.WriteLine(string.Join(", ", s.Reverse()));
  }
}
Reverseメソッドを使ってSortedSet内の要素を逆順で文字列として結合する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of Integer) From {5, 1, 0, 3, 4, 2}

    ' デフォルトの順序でSortedSet内の要素を文字列として結合する
    Console.WriteLine(String.Join(", ", s))

    ' デフォルトとは逆順でSortedSet内の要素を文字列として結合する
    Console.WriteLine(String.Join(", ", s.Reverse()))
  End Sub
End Class
実行結果
0, 1, 2, 3, 4, 5
5, 4, 3, 2, 1, 0
Reverseメソッドを使ってSortedSet内の要素の一部分を逆順で取得する
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    var s = new SortedSet<int>() {5, 1, 0, 3, 4, 2};

    // デフォルトの順序でSortedSet内の要素の一部分を取得する
    // (最初の要素から1つスキップしたのち、3つ分を取得する)
    Console.WriteLine(string.Join(", ", s.Skip(1).Take(3)));

    // デフォルトとは逆順でSortedSet内の要素の一部分を取得する
    // (最初の要素から1つスキップしたのち、3つ分を取得する)
    Console.WriteLine(string.Join(", ", s.Reverse().Skip(1).Take(3)));
  }
}
Reverseメソッドを使ってSortedSet内の要素の一部分を逆順で取得する
Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of Integer) From {5, 1, 0, 3, 4, 2}

    ' デフォルトの順序でSortedSet内の要素の一部分を取得する
    ' (最初の要素から1つスキップしたのち、3つ分を取得する)
    Console.WriteLine(String.Join(", ", s.Skip(1).Take(3)))

    ' デフォルトとは逆順でSortedSet内の要素の一部分を取得する
    ' (最初の要素から1つスキップしたのち、3つ分を取得する)
    Console.WriteLine(String.Join(", ", s.Reverse().Skip(1).Take(3)))
  End Sub
End Class
実行結果
1, 2, 3
4, 3, 2

部分集合(サブセット)の取得

GetViewBetweenメソッドを使うと、指定した範囲に該当する部分集合をSortedSet<T>として取得出来ます。

GetViewBetweenメソッドを使ってSortedSet内の指定した範囲にある数値の部分集合を取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<int>() {5, 1, 0, 3, 4, 2};

    // 最小で1、最大で4の範囲に該当する部分集合を取得する
    Console.WriteLine(string.Join(", ", s.GetViewBetween(1, 4)));
  }
}
GetViewBetweenメソッドを使ってSortedSet内の指定した範囲にある数値の部分集合を取得する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of Integer) From {5, 1, 0, 3, 4, 2}

    ' 最小で1、最大で4の範囲に該当する部分集合を取得する
    Console.WriteLine(String.Join(", ", s.GetViewBetween(1, 4)))
  End Sub
End Class
実行結果
1, 2, 3, 4
GetViewBetweenメソッドを使ってSortedSet内の指定した範囲にある文字列の部分集合を取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<string>() {"Adams", "Cyndy", "Dave", "Bob", "Charlie", "Elliott", "Becky", "Alice", "Diana", "Eve"};

    Console.Write("Set: ");
    Console.WriteLine(string.Join(", ", s));

    // 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    Console.Write("GetViewBetween(B, E): ");
    Console.WriteLine(string.Join(", ", s.GetViewBetween("B", "E")));

    // 最小で"Ae"、最大で"Bz"の範囲に該当する部分集合を取得する
    Console.Write("GetViewBetween(Ae, Bz): ");
    Console.WriteLine(string.Join(", ", s.GetViewBetween("Ae", "Bz")));
  }
}
GetViewBetweenメソッドを使ってSortedSet内の指定した範囲にある文字列の部分集合を取得する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of String) From {"Adams", "Cyndy", "Dave", "Bob", "Charlie", "Elliott", "Becky", "Alice", "Diana", "Eve"}

    Console.Write("Set: ")
    Console.WriteLine(String.Join(", ", s))

    ' 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    Console.Write("GetViewBetween(B, E): ")
    Console.WriteLine(String.Join(", ", s.GetViewBetween("B", "E")))

    ' 最小で"Ae"、最大で"Bz"の範囲に該当する部分集合を取得する
    Console.Write("GetViewBetween(Ae, Bz): ")
    Console.WriteLine(String.Join(", ", s.GetViewBetween("Ae", "Bz")))
  End Sub
End Class
実行結果
Set: Adams, Alice, Becky, Bob, Charlie, Cyndy, Dave, Diana, Elliott, Eve
GetViewBetween(B, E): Becky, Bob, Charlie, Cyndy, Dave, Diana
GetViewBetween(Ae, Bz): Alice, Becky, Bob

部分集合のSortedSetと元のSortedSetに対する変更

GetViewBetweenメソッドは、引数で指定した範囲に該当する部分のビューを返します。 SortedSetの一部をコピーしたものが返されるわけではないため、元になったSortedSetに変更を加えると、GetViewBetweenメソッドで取得したサブセットにも反映されます。

GetViewBetweenメソッドで部分集合を取得した後に元のSortedSetに変更を加える
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<string>() {"Alice", "Eve", "Charlie", "Bob", "Dave"};

    Console.Write("s: ");
    Console.WriteLine(string.Join(", ", s));

    // 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    var view = s.GetViewBetween("B", "E");

    Console.Write("view: ");
    Console.WriteLine(string.Join(", ", view));

    // 元の集合に変更を加える
    // (上で取得した部分集合にも影響する)
    s.Add("Diana");

    Console.Write("view: ");
    Console.WriteLine(string.Join(", ", view));
  }
}
GetViewBetweenメソッドで部分集合を取得した後に元のSortedSetに変更を加える
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of String)() From {"Alice", "Eve", "Charlie", "Bob", "Dave"}

    Console.Write("s: ")
    Console.WriteLine(String.Join(", ", s))

    ' 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    Dim view As SortedSet(Of String) = s.GetViewBetween("B", "E")

    Console.Write("view: ")
    Console.WriteLine(String.Join(", ", view))

    ' 元の集合に変更を加える
    ' (上で取得した部分集合にも影響する)
    s.Add("Diana")

    Console.Write("view: ")
    Console.WriteLine(String.Join(", ", view))
  End Sub
End Class
実行結果
s: Alice, Bob, Charlie, Dave, Eve
view: Bob, Charlie, Dave
view: Bob, Charlie, Dave, Diana

また逆に、GetViewBetweenメソッドで取得したサブセットに変更を加えることもでき、この変更は元になったSortedSetにも反映されます。

GetViewBetweenメソッドで取得した部分集合を通して元のSortedSetに変更を加える
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<string>() {"Alice", "Eve", "Charlie", "Bob", "Dave"};

    Console.Write("s: ");
    Console.WriteLine(string.Join(", ", s));

    // 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    var view = s.GetViewBetween("B", "E");

    Console.Write("view: ");
    Console.WriteLine(string.Join(", ", view));

    // 取得した部分集合に変更を加える
    // (取得した部分集合の元になるSortedSetにも影響する)
    view.Add("Cyndy");

    Console.Write("s: ");
    Console.WriteLine(string.Join(", ", s));
  }
}
GetViewBetweenメソッドで取得した部分集合を通して元のSortedSetに変更を加える
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of String)() From {"Alice", "Eve", "Charlie", "Bob", "Dave"}

    Console.Write("s: ")
    Console.WriteLine(String.Join(", ", s))

    ' 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    Dim view As SortedSet(Of String) = s.GetViewBetween("B", "E")

    Console.Write("view: ")
    Console.WriteLine(String.Join(", ", view))

    ' 取得した部分集合に変更を加える
    ' (取得した部分集合の元になるSortedSetにも影響する)
    view.Add("Cyndy")

    Console.Write("s: ")
    Console.WriteLine(String.Join(", ", s))
  End Sub
End Class
実行結果
s: Alice, Bob, Charlie, Dave, Eve, 
s: Alice, Bob, Charlie, Cyndy, Dave, Eve, 

SortedSetには、そのSortedSetがGetViewBetweenメソッドによって取得されたビューなのかどうかを判断するプロパティやメソッドは用意されていません。 そのため、GetViewBetweenメソッドによって取得したSortedSetに対して変更を行う際には、影響範囲に注意する必要があります。

.NET 5以降では、IReadOnlySet<T>インターフェイスが新たに導入されるため、GetViewBetweenメソッドで取得したSortedSetをIReadOnlySet<T>にキャストすることにより、読み取り専用のビューとして扱うことができるようになります。


GetViewBetweenメソッドで指定した範囲外の値をサブセットに対して追加しようとした場合、ArgumentOutOfRangeExceptionがスローされます。 GetViewBetweenメソッドで取得したサブセットに対しては、その範囲内に対する変更のみが行えます。

GetViewBetweenメソッドで指定した範囲外に変更を加えようとするとArgumentOutOfRangeExceptionとなる
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var s = new SortedSet<string>() {"Alice", "Eve", "Charlie", "Bob", "Dave"};

    // 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    var view = s.GetViewBetween("B", "E");

    // ビューの範囲外の値を追加しようとする
    view.Add("Adams");
  }
}
GetViewBetweenメソッドで指定した範囲外に変更を加えようとするとArgumentOutOfRangeExceptionとなる
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim s As New SortedSet(Of String)() From {"Alice", "Eve", "Charlie", "Bob", "Dave"}

    ' 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    Dim view As SortedSet(Of String) = s.GetViewBetween("B", "E")

    ' ビューの範囲外の値を追加しようとする
    view.Add("Adams")
  End Sub
End Class
実行結果
Unhandled exception. System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'item')
   at System.Collections.Generic.SortedSet`1.TreeSubSet.AddIfNotPresent(T item)
   at System.Collections.Generic.SortedSet`1.Add(T item)
   at Sample.Main() in /home/smdn/samplecodes/dotnet/cs/test.cs:line 13

容量

HashSet・SortedSetでは、それ以上要素の追加や削除をする必要がなくなった場合に、TrimExcessメソッドを呼び出すことによって、HashSet・SortedSetが内部的に確保しているバッファを最小化することができ、不要な容量を減らすことができます。

また、HashSet・SortedSetに格納する最大要素数を事前に見積もれる場合は、EnsureCapacityメソッドを呼び出すことによって、あらかじめ指定したサイズのバッファを確保させておくことができます。 これにより、要素の追加に伴うバッファの再割当てとコピーを減らすことができます。 EnsureCapacityメソッドは、.NET Standard 2.1/.NET Core 2.1以降で利用できます。

容量と容量の縮小・確保について詳しくは、Listでの例を参照してください。