C#とJavaバックグラウンドから来ているので、リストが同質であることに慣れているので、それは私には理にかなっています。 C#でdynamic
キーワードをいじり始めたとき、C#4.0以降、異種のリストが存在する可能性があることに気付きました。
List<dynamic> heterogeneousList
私の質問は、ポイントは何ですか?異種のリストは処理を行うときにオーバーヘッドがはるかに多くなり、異なるタイプを1か所に格納する必要がある場合は、異なるデータ構造が必要になる可能性があります。私の初心者は醜い顔を育てていますか、それとも本当に異種のリストがあると便利なときがありますか?
Oleg Kiselyov、RalfLämmel、およびKeean Schupkeによる論文 Strongly Typed Heterogenous Collections には、Haskellでの異種リストの実装だけでなく、HListsをいつ、どのように、どのように使用するかという動機付けの例も含まれています。特に、タイプセーフなコンパイル時チェックデータベースアクセスにそれを使用しています。 (実際にLINQを考えてください。彼らが参照している論文はisLINQにつながったErik MeijerらのHaskell論文です。
HLists論文の紹介段落からの引用:
以下は、異種のコレクションを必要とする典型的な例の制限のないリストです。
- 異なるタイプのエントリを格納することになっているシンボルテーブルは、異種混合です。これは有限マップであり、結果の型は引数の値によって異なります。
- XML要素は異種混合型です。実際、XML要素は、正規表現と1-ambiguityプロパティによって制約される入れ子になったコレクションです。
- SQLクエリによって返される各行は、列名からセルへの異種マップです。クエリの結果は、異種の行の同種のストリームです。
- 高度なオブジェクトシステムを関数型言語に追加するには、拡張可能なレコードとサブタイプおよび列挙インターフェイスを組み合わせる種類の異種コレクションが必要です。
質問で挙げた例は、Wordが一般的に使用されているという意味で、実際には異種のリストではありません。それらは弱く型付けされたまたは型付けされていないリストです。実際、すべての要素が同じタイプなので、実際には同種リストです:object
またはdynamic
。次に、キャストまたはチェックされていないinstanceof
テストなどを強制的に実行して、要素を実際に意味のある方法で操作できるようにします。
一言で言えば、異種のコンテナは、ランタイムパフォーマンスと柔軟性をトレードオフします。特定の種類のものに関係なく「ものリスト」を作成したい場合は、異質性が適しています。 Lispは特徴的に動的に型付けされており、ほとんどすべてがとにかくボックス化された値のcons-listであるため、小さなパフォーマンスヒットが予想されます。 LISPの世界では、プログラマーの生産性はランタイムパフォーマンスよりも重要であると考えられています。
動的に型付けされた言語では、追加されたすべての要素の型チェックが必要になるため、homogeneousコンテナは実際には異種の言語に比べてわずかなオーバーヘッドがあります。
より優れたデータ構造の選択に関する直感は要点です。一般的に言えば、コードに配置できるコントラクトが多ければ多いほど、それがどのように機能するかを理解し、信頼性と保守性が向上します。あれは。。。になる。ただし、場合によっては実際に行うが異種コンテナを必要とする場合があり、必要な場合はそれを許可する必要があります。
関数型言語(LISPなど)では、パターンマッチングを使用して、リスト内の特定の要素がどうなるかを決定します。 C#でこれに相当するのは、要素のタイプをチェックし、それに基づいて操作を実行するif ... elseifステートメントのチェーンです。言うまでもなく、関数型パターンマッチングは、実行時の型チェックよりも効率的です。
ポリモーフィズムの使用は、パターンマッチングにより近い一致になります。つまり、リストのオブジェクトを特定のインターフェイスに一致させ、オブジェクトごとにそのインターフェイスで関数を呼び出すことです。別の代替方法は、特定のオブジェクトタイプをパラメーターとして受け取る一連のオーバーロードメソッドを提供することです。 Objectをパラメーターとして受け取るデフォルトのメソッド。
public class ListVisitor
{
public void DoSomething(IEnumerable<dynamic> list)
{
foreach(dynamic obj in list)
{
DoSomething(obj);
}
}
public void DoSomething(SomeClass obj)
{
//do something with SomeClass
}
public void DoSomething(AnotherClass obj)
{
//do something with AnotherClass
}
public void DoSomething(Object obj)
{
//do something with everything els
}
}
このアプローチは、LISPパターンマッチングの近似を提供します。ビジターパターン(ここに実装されているように、異種リストの使用法の良い例です)。別の例は、メッセージディスパッチの場合で、優先キューに特定のメッセージのリスナーがあり、責任の連鎖を使用して、ディスパッチャーがメッセージを渡し、メッセージに一致する最初のハンドラーがそれを処理します。
反対側は、メッセージに登録するすべてのユーザーに通知しています(たとえば、MVVMパターンでViewModelの疎結合に一般的に使用されるEvent Aggregatorパターン)。次の構文を使用します
IDictionary<Type, List<Object>>
辞書に追加する唯一の方法は関数です
Register<T>(Action<T> handler)
(そしてオブジェクトは実際には渡されたハンドラーへのWeakReferenceです)。コンパイル時に、閉じた型がどうなるかわからないので、ここではList <Object>を使用する必要があります。ただし、実行時に、そのTypeが辞書のキーになることを強制できます。私が呼び出すイベントを起動したいとき
Send<T>(T message)
そして再び私はリストを解決します。とにかくキャストする必要があるため、List <dynamic>を使用する利点はありません。ご覧のとおり、両方のアプローチにはメリットがあります。メソッドオーバーロードを使用してオブジェクトを動的にディスパッチする場合は、動的がそれを行う方法です。関係なくキャストを強制される場合は、Objectを使用することもできます。
LISPはリスト以外のデータ構造を持たないというISTRであるため、何らかのタイプの集約データオブジェクトが必要な場合は、異種混合リストになるようにhaveになります。他の人が指摘したように、それらは送信またはストレージのいずれかのためにデータをシリアル化するためにも役立ちます。優れた機能の1つは、これらもオープンエンドであるため、パイプとフィルターの類推に基づくシステムで使用でき、固定データオブジェクトやワークフロートポロジーを必要とせずに、一連の処理ステップでデータを拡張または修正できることです。 。
異質性は実行時のオーバーヘッドを伴うことは正しいですが、より重要なことに、タイプチェッカーによって提供されるコンパイル時の保証が弱められます。それでも、代替案がさらに高額になる問題がいくつかあります。
私の経験では、ファイルやネットワークソケットなどを介して生のバイトを処理するときに、このような問題に遭遇することがよくあります。
実際の例を示すために、 futures を使用した分散計算のシステムを考えます。個々のノードのワーカーは、任意のシリアル化可能なタイプの作業を生成して、そのタイプのフューチャーを生み出すことができます。システムは背後で作業をピアに送信し、その作業の単位が特定の未来に関連付けられているレコードを保存します。このレコードは、その作業に対する回答が返されたときに入力する必要があります。
これらの記録はどこに保存できますか?直感的に、あなたが望むのはDictionary<WorkId, Future<TValue>>
、ただしこれにより、システム全体で1種類の先物のみを管理するように制限されます。より適切なタイプはDictionary<WorkId, Future<dynamic>>
、未来を強制するときにワーカーが適切なタイプにキャストできるため。
注:この例は、サブタイピングのないHaskellの世界からのものです。 C#のこの特定の例にもっと慣用的な解決策があるとしても、私は驚かないでしょうが、うまくいけば、それはまだ説明的なものです。