次の方法で共有


働くプログラマ:

.NET のコレクション: C5 入門

Ted Neward

 

Ted Neward告白があります。

日中、私はコンサルティング会社の Neudesic LLC でおとなしい .NET 開発者として働いています。しかし夜、妻と息子が眠った後、ノート PC を持って家をこっそり抜け出し、秘密の隠れ家 (148 番通りのデニーズ) へ行って、Java コードを書いているのです。

そう、私は .NET 開発者と Java (より正確には Java 仮想マシン (JVM)) 開発者の二重生活を送っています。このような二重生活で得られる面白い恩恵の 1 つは、Microsoft .NET Framework のさまざまな分野の中に JVM に取り入れられるすばらしいアイデアがある分野がわかることです。そのような分野の 1 つがカスタム属性です。JVM では、"注釈" という名前で、Java5 (2005 年ごろ) で採用されました。逆もあります。実際、JVM に取り入れられていても、CLR と .NET 基本クラス ライブラリ (BCL) には取り入れていない (より慎重に言うなら、少なくともあまり取り入れられていない) ものがあります。その 1 つが、.NET BCL の中核をなす、コレクションです。

コレクション: 批評

.NET コレクションの汚点は、つまらないものを 2 回も BCL チームが作成しなければならなかったという事実です。1 回目は、ジェネリックが使用できるようになる前の .NET Framework 1.0/1.1 のリリース時、2 回目は、厳密に型指定されないバージョンのコレクションはばかばかしいという理由からジェネリックが CLR の一部になった .NET Framework 2.0 のリリース時です。つまり、それらの 1 つが問題を含んだまま放置されていたということで、実質的に、ライブラリの機能拡張や追加が行われることになりました (Java では、ジェネリックでないバージョンをジェネリック バージョンに実質的に "置き換える" ことで、この問題を回避しました。Java でジェネリックを使用する方法 (今回のテーマではありません) では、そうするしかありませんでした)。Visual Studio 2008 と C# 3.0 の LINQ 由来の強化は別として、コレクション ライブラリは、厳密に型指定されたバージョンの新しい名前空間 (System.Collections.Generic、SCG) に System.Collections クラスが再実装された 2.0 リリース以降、ほとんど評価されていません。

しかし、さらに重要なことは、.NET コレクションの設計が、コレクションの設計やその拡張方法に十分な配慮を行っているというよりも、1.0 リリースの一環として広く実用的で役立つものになることに重点を置いていたことです。これが、.NET Framework が Java の世界と (おそらく意図せずに) 根本的に異なる分野です。Java 1.0 がリリースされたとき、基本的かつ実用的なコレクションのセットが含まれていました。しかし、いくつかの設計上の問題がありました (もっともたちが悪かったのは、後入れ先出しの Stack クラスが、本来 ArrayList である Vector クラスを直接拡張するという決定でした)。Java 1.1 のリリース後、Sun Microsystems のエンジニアの何名かが、コレクション クラスの書き直しに尽力し (現在の Java Collections)、それを Java 1.2 に含めてリリースしました。

とにかく、.NET Framework は、既存の SCG クラスと少なくとも大部分で互換性がある方法で、コレクション クラスを刷新する時期に来ています。さいわい、デンマークのコペンハーゲン IT 大学の研究員が、SCG クラスをすばらしく継承し、補完する "Copenhagen Comprehensive Collection Classes for C#"(通称 "C5") というライブラリを作成しました。

C5 の普及状況

まず、バージョン履歴や C5 に関する書籍 (PDF) へのリンクを探す場合、Web (itu.dk/research/c5) に C5 の情報があります。ただし、書籍は数リリース前を対象にしたものです。また、(今では広く利用されている) Install-Package コマンドで「Install-Package C5」と入力するだけで、NuGet から C5 を入手することもできます。C5 は、Visual Studio と Mono の両方で使用できるように記述されています。NuGet でパッケージをインストールすると、C5.dll アセンブリと C5Mono.dll アセンブリの両方への参照が追加されます。2 つは冗長なので、不要な参照を削除してください。一連のテストを実行して C5 のコレクションを調査するため、Visual C# テスト プロジェクトを作成し、そのプロジェクトに C5 を追加しました。それ以外に、コードで特に変更した点は、2 つの "using" ステートメントです。C5 ドキュメントでも、次のように記述するものとしています。

using SCG = System.Collections.Generic;
using C5;

エイリアスを使用する理由は単純です。C5 では、SCG バージョンと同じ名前のインターフェイスとクラスをいくつか "再実装" しています。そのため、古い方にはエイリアスを定義して、短いプレフィックスを使用して引き続き使用できるようにしています (たとえば、C5 バージョンの IList<T> に対し、SCG の "従来" バージョンは SCG.IList<T> になります)。

ちなみに、弁護士対策ですが、C5 は MIT ライセンスの下にオープン ソース化されているので、GNU General Public License (GPL) や GNU Lesser General Public License (LGPL) に基づく場合以上に、C5 クラスを変更、強化できます。

C5 の設計概要

C5 設計アプローチを見ると、コレクションが特定のコレクションに想定するインターフェイスと動作を記述したインターフェイス層と、必要な複数のインターフェイスの実際のバッキング コードを提供する実装層の 2 つの "レベル" に分けられており、SCG スタイルに似ています。SCG のクラスもこの考え方に似ていますが、まったく似ていない部分もあります。たとえば、SortedSet<T> (配列ベース、リンク リスト ベース、またはハッシュ ベースを選択するもので、それぞれ挿入や移動などの操作の点で異なる特徴があります) の実装に関しての柔軟性はありません。SCG のクラスでは、単に、特定の型のコレクションがない場合があります。たとえば、循環キュー (キューの最後の項目が移動されると、イテレーションは再びキューの最初に回り込みます) や単純な "バッグ" コレクション (項目を含める以外の機能がなく、並べ替えやインデックスなどの不要なオーバーヘッドを回避する) などです。

確かに、平均的な .NET 開発者にとっては、これはたいした不足ではないようですが、多くのアプリケーションでは、パフォーマンスに重点が置かれるようになるにつれて、目前の問題に合わせた正しい型のコレクションを選択することが重要になっています。作成されると頻繁に移動されるコレクションか、あるいは、頻繁に追加や削除が行われてもめったに移動されないコレクションかといった選択です。アプリケーション機能の中核をなす (またはアプリケーションそのものである) コレクションの場合、2 つの違いは、「アプリが悲鳴を上げている」と「ユーザーは気に入るが、動作が遅いと感じるだろう」ほどの違いを生みます。

C5 では、開発者が "実装ではなくインターフェイスに対してコードを作成する" というのが基本原則の 1 つとしてあります。そのため、基盤となるコレクションが提供するものを記述した 10 種類以上ものインターフェイスが用意されています。ICollection<T> がすべての基盤であり、基本的なコレクションの動作を保証していますが、ここからは、手始めに IList<T>、IIndexed<T>、ISorted<T>、ISequenced<T> などについて見ていきます。インターフェイス、インターフェイスどうしの関係、およびインターフェイスの保証全般の完全なリストを以下に示します。

  • SCG.IEnumerable<T> は、項目を列挙できます。すべてのコレクションとディクショナリが列挙可能です。
  • IDirectedEnumerable<T> は、項目を逆の順序で列挙する逆向き列挙を提供します。
  • ICollectionValue<T> はコレクションの値です。変更はサポートしませんが、列挙可能で、項目の数がわかり、複数の項目を配列にコピーできます。
  • IDirectedCollectionValue<T> は、逆方向のコレクション値に反転できるコレクションの値です。
  • IExtensible<T> は、項目を追加できるコレクションです。
  • IPriorityQueue<T> は、最小および最大の項目を効率的に検索 (および削除) できる拡張可能なコレクションです。
  • ICollection<T> は、項目の削除もできる拡張可能なコレクションです。
  • ISequenced<T> は、特定のシーケンスで項目が表示されるコレクションです (挿入の順序または項目の順序で決まります)。
  • IIndexed<T> は、項目にインデックスでアクセスできる順序付けられたコレクションです。
  • ISorted<T> は、項目が昇順で表示される順序付けられたコレクションです。項目を比較して順序が決まります。(コレクション内の) 特定項目の前後の項目を効率的に検索できます。
  • IIndexedSorted<T> は、インデックスが付けられ、並べ替えられたコレクションです。特定の項目 x 以上またはそれに等しい項目の数を判断するのに効果的です。
  • IPersistentSorted<T> は、スナップショット (元のコレクションを更新しても影響を受けない読み取り専用コピー) を効率的に作成できる、並べ替えられたコレクションです。
  • IQueue<T> は、インデックスもサポートする先入れ先出し (FIFO) のキューです。
  • IStack<T> は、後入れ先出し (LIFO) のスタックです。
  • IList<T> は、インデックスが付けられ、順序付けられたコレクションです。項目の順序は挿入と削除で決まります。SCG.IList<T> から派生しています。

実装に関して、C5 は循環キュー、配列ベースのリスト、リンク リスト ベースのリストのほかにも、ハッシュ ベースの配列リスト、ハッシュ ベースのリンク リスト、ラップされた配列、並べ替えられた配列、ツリー ベースのセット、バッグなど、多数を実装しています。

C5 のコーディング スタイル

さいわい、C5 のためにコーディング スタイルを大幅に変更する必要はありません。さらにありがたいことに、すべての LINQ 処理がサポートされている (SCG インターフェイスの上位に構築されており、LINQ 拡張メソッドによりキーが取り除かれる) ので、場合によっては、周囲のコードを変更せずに、構築時に C5 コレクションにドロップできます。この例として、図 1 をご覧ください。

図 1 C5 の概要

// These are C5 IList and ArrayList, not SCG
IList<String> names = new ArrayList<String>();
names.AddAll(new String[] { "Hoover", "Roosevelt", "Truman",
  "Eisenhower", "Kennedy" });
// Print list:
Assert.AreEqual("[ 0:Hoover, 1:Roosevelt, 2:Truman, 3:Eisenhower," +
  " 4:Kennedy ]", names.ToString());
// Print item 1 ("Roosevelt") in the list
Assert.AreEqual("Roosevelt", names[1]);
Console.WriteLine(names[1]);
// Create a list view comprising post-WW2 presidents
IList<String> postWWII = names.View(2, 3);
// Print item 2 ("Kennedy") in the view
Assert.AreEqual("Kennedy", postWWII[2]);
// Enumerate and print the list view in reverse chronological order
Assert.AreEqual("{ Kennedy, Eisenhower, Truman }",
  postWWII.Backwards().ToString());

C5 のドキュメントを見たことがない場合でも、この例で行われていることは簡単にわかります。

C5 と SCG コレクションの実装の比較

今回は、C5 についての氷山の一角にすぎません。次回のコラムでは、SCG の代わりに C5 コレクション実装を使用するいくつかの応用例と、使用するメリットを説明します。ただし、次回を待たずに、NuGet を使用し、C5 をダウンロードして、自分で試してみることをお勧めします。その間に楽しめるものが多数あります。

コーディングを楽しんでください。

Ted Neward は、Neudesic LLC のアーキテクチャ コンサルタントです。これまでに 100 本を超える記事を執筆している Ted は、さまざまな書籍を執筆および共同執筆していて、『Professional F# 2.0』(Wrox、2010 年) もその 1 つです。F# MVP、有名な Java 専門家でもあり、世界中の Java と .NET 両方のカンファレンスで講演をしています。彼は定期的にコンサルティングを行い、開発者を指導しています。彼の連絡先は ted@tedneward.com (英語のみ) です。また、彼がチームの作業に加わることに興味がある場合の連絡先は、Ted.Neward@neudesic.com (英語のみ) です。彼のブログは blogs.tedneward.com (英語) で、Twitter は twitter.com/tedneward (英語) でフォローすることができます。

この記事のレビューに協力してくれた技術スタッフの Immo Landwerth に心より感謝いたします。