LINQ to Objects & Interactive Extensions & linq.js 全メソッド概説

@ITに以前書いたLINQの基礎知識の話が載りました -> LINQの仕組み&遅延評価の正しい基礎知識 - @IT。ああ、もっとしっかり書いていれば(図もへっぽこだし)、と思ったり思わなかったり。それでも校正していただいたのと、細部は修正してあるので、元のものよりも随分と読みやすいはずです。そういえばで1月頭の話なんですね、姉妹編としてRxの基礎知識もやるつもりだったのにまだやってないよ!

ところでそもそも基礎知識といったら標準クエリ演算子が何をできるかではないのでしょうか?知ってるようで知らない標準クエリ演算子。101 LINQ SamplesもあるしMSDNのリファレンスは十分に充実していますが、しかし意外と見逃しもあるかもしれません。また、Interactive Extensionsで何が拡張されているのかは知っていますか?ついでにJS実装のlinq.jsには何があるのか知っていますか?

そんなわけで、LINQ to Objects、Ix、linq.jsの全メソッドを一行解説したいと思います。

LINQ to Objects

いわゆる、標準クエリ演算子。.NET 3.5から使えます。.NET4.0からはZipメソッドが追加されました。なお、サンプルと実行例はlinq.js Referenceに「完全に」同じ挙動をするJS実装での例がありますので、そちらを参照にどうぞ。こういう場合はJS実装だと便利ですね。

Aggregate 汎用的な値算出
All 条件に全て一致するか
Any 条件に一つでも一致するか、引数なしの場合は空かどうか
AsEnumerable IEnumerable<T>へアップキャスト
Average 平均
Cast 値のダウンキャスト、主な用途はIEnumerableからIEnumerable<T>への変換
Concat 引数のシーケンスを後ろに連結
Contains 値が含まれているか、いわばAnyの簡易版
Count シーケンスの件数
DefaultIfEmpty シーケンスが空の場合、デフォルト値を返す(つまり長さ1)
Distinct 重複除去
ElementAt 指定インデックスの要素の取得
ElementAtOrDefault 指定インデックスの要素の取得、なければデフォルト値を返す
Empty 空シーケンスの生成
Except 差集合・差分だけ、集合なので重複は除去される
First 最初の値の取得、ない場合は例外が発生
FirstOrDefault 最初の値を取得、ない場合はデフォルト値を返す
GroupBy グループ化、ToLookupの遅延評価版(ただしストリーミングでの遅延評価ではない)
GroupJoin 右辺をグループにして結合、外部結合をしたい時にDefaultIfEmptyと合わせて使ったりもする
Intersect 積集合・共通の値だけ、集合なので重複は除去される
Join 内部結合
Last 最後の値を取得、ない場合は例外が発生
LastOrDefault 最後の値を取得、ない場合はデフォルト値を返す
LongCount シーケンスの件数、longなので長い日も安心
Max 最大値
Min 最小値
OfType 指定した型の値だけを返す、つまりWhereとisが組み合わさったようなもの
OrderBy 昇順に並び替え
OrderByDescending 降順に並び替え
Range 指定個数のintシーケンスの生成
Repeat 一つの値を繰り返すシーケンスの生成
Reverse 逆から列挙
Select 射影、関数の第二引数はインデックス
SelectMany シーケンスを一段階平らにする、モナドでいうbind
SequenceEqual 二つのシーケンスを値で比較
Single 唯一の値を取得、複数ある場合は例外が発生
SingleOrDefault 唯一の値を取得、複数ある場合はデフォルト値を返す
Skip 指定個数だけ飛ばす
SkipWhile 条件が正のあいだ飛ばす
Sum 合計
Take 指定個数列挙、シーケンスの個数より多く指定した場合はシーケンスの個数分だけ
TakeWhile 条件が正のあいだ列挙
ThenBy 同順の場合のソートキーの指定、昇順に並び替え
ThenByDescending 同順の場合のソートキーの指定、降順に並び替え
ToArray 配列に変換
ToDictionary 辞書に変換
ToList リストに変換
ToLookup 不変のマルチ辞書(一つのキーに複数の値を持つ)に変換
Union 和集合・両方の値全て、集合なので重複は除去される
Where フィルタ
Zip 二つのシーケンスの結合、長さが異なる場合短いほうに合わされる

暗記する必要はなくて、なんとなくこういうのがあってこんな名前だったかなー、とぐらいに覚えておけば、IntelliSenseにお任せできるので、それで十分です。

リスト処理という観点からみるとLINQはかなり充実しているわけですが、更に他の言語と比較した場合の特色は、やはりクエリ構文。SelectManyへの構文は多くの言語が備えていますが(モナドの驚異を参照のこと、LINQはLINM:言語統合モナドである、というお話)、SQLの構文をベースにしたJoin、GroupBy、OrderByへの専用記法は、意外と、というか普通に便利。

特にJoinはあってよかったな、と思います、インメモリで色々なところからデータ引っ張ってきて結合などすると特に。一つぐらいの結合なら別にメソッド構文でいいのですが、フツーのSQLと同じように大量のjoinを並べる場合に、クエリ構文じゃないとシンドい。インメモリからデータベースまで統一的な記法で扱える、ということの凄さを実感するところ。

といっても、普段はほとんどメソッド構文で書いてるんですけどねー。あくまで、込み入った状況になるときだけクエリ構文にしています。クエリ構文では表現できないものが結構多いわけで、わざわざ、これはクエリ構文だけで表現できるからクエリ構文にするかー、とか考えるのもカッタルイので。あと、単純にIntelliSenseでポコポコ打ってるほうが快適、というのもあります。

クエリ構文は、モナドへの記法というよりも、強力なリスト内包表記といった印象も、HaskellへのOrder By, Group Byのペーパー見て思ったりなんかしたりして。

Ix

Ix(Interactive Extensions)はReactive Extensionsで、現在は実験的なものとして提供されている、Enumerableの拡張メソッド群。NuGetのIx_Experimental-Mainで入れるのが使いやすい感じ。InfoQ: LINQ to Objectsのためのインタラクティブエクステンションに解説が少し出ていましたが、少し不足していたり、間違っていたり(DoWhileとTakeWhileは一見似ていますが、挙動は全然異なるし、Forは別に全く興味深くなくSelectManyと同じです)したので、こちらの方が正しいです(キリッ

Buffer 指定個数分に区切って配列で値を列挙
Case 引数のIDictionaryを元に列挙するシーケンスを決める、辞書に存在しない場合はEmpty
Catch 例外発生時に代わりに後続のシーケンスを返す
Concat 可変長引数を受け入れて連結する生成子、拡張メソッド版はシーケンスのシーケンスを平らにする
Create getEnumeratorを渡し任意のIEnumerableを生成する、といってもEnumerator.Createがないため、あまり意味がない
Defer シーケンスの生成をGetEumerator時まで遅延
Distinct 比較キーを受け入れるオーバーロード
DistinctUntilChanged 同じ値が続くものを除去
Do 副作用として各値にActionを適用し、値をそのまま列挙
DoWhile 一度列挙後に条件判定し、合致すれば再列挙
Expand 幅優先探索でシーケンスを再帰的に平らにする
Finally 列挙完了時に指定したActionを実行
For SelectManyと一緒なので存在意義はない(Rxと鏡にするためだけに存在)
ForEach foreach、関数の第二引数はインデックス
Generate forループを模した初期値、終了判定、増加関数、値成形関数を指定する生成子
Hide IEnumerable<T>に変換、具象型を隠す
If 条件が正なら指定したシーケンスを、負なら指定したシーケンス、もしくはEmptyで列挙する
IgnoreElements 後に続くメソッドに何の値も流さない
IsEmpty シーケンスが空か、!Any()と等しい
Max IComparer<T>を受け入れるオーバーロード
MaxBy 指定されたキーで比較し最大値だった値を返す
Memoize メモ化、複数回列挙する際にキャッシュされた値を返す
Min IComparer<T>を受け入れるオーバーロード
MinBy 指定されたキーで比較し最小値だった値を返す
OnErrorResumeNext 例外が発生してもしなくても後続のシーケンスを返す
Publish ShareとMemoizeが合わさったような何か
Repeat 無限リピート生成子、拡張メソッドのほうは列挙後に無限/指定回数最列挙
Retry 例外発生時に再度列挙する
Return 単一シーケンス生成子
Scan Aggregateの算出途中の値も列挙する版
SelectMany 引数を使わず別のシーケンスに差し替えるオーバーロード
Share 列挙子を共有
SkipLast 後ろからn個の値をスキップ
StartWith 先頭に値を連結
TakeLast 後ろからn個の値だけを列挙
Throw 例外が発生するシーケンス生成子
Using 列挙完了後にDisposeするためのシーケンス生成子
While 列挙前に条件判定し合致したら列挙し、終了後再度条件判定を繰り返す生成子

みんな実装したことあるForEachが載っているのが一番大きいのではないでしょうか。別に自分で実装するのは簡単ですが、公式に(といってもExperimental Releaseですが)あると、全然違いますから。なお、何故ForEachが標準クエリ演算子にないのか、というのは、“foreach” vs “ForEach” - Fabulous Adventures In Codingによれば副作用ダメ絶対とのことで。納得は……しない。

Ixに含まれるメソッドは標準クエリ演算子では「できない」もしくは「面倒くさい」。Ixを知ることは標準だけでは何ができないのかを知ること。何ができないのかを知っていれば、必要な局面でIxを使うなり自前実装するなりといった対応がすぐに取れます、無理に標準クエリ演算子をこねくり回すことなく。例えばBufferやExpandは非常に有益で、使いたいシチュエーションはいっぱいあるんですが、標準クエリ演算子ではできないことです。

While, DoWhileとTakeWhileの違いは条件判定する箇所。While,DoWhileは列挙完了前/後に判定し、判定がtrueならシーケンスを再び全て列挙する。TakeWhileは通る値で毎回判定する。

PublishとMemoizeの違いは難解です。Memoizeは直球そのままなメモ化なんですが、Publishが凄く説明しづらくて……。Enumerator取得まではShareと同じく列挙子の状態は共有されてるんですが、取得後はMemoizeのようにキャッシュした値を返すので値の順番は保証される、といった感じです。うまく説明できません。

存在意義が微妙なものも、それなりにありますね。例えばIfとCaseとForなどは、正直、使うことはないでしょう。Usingも、これを使うなら別メソッドに分けて、普通にusing + yield returnで書いてしまうほうが良いと私は考えています。

Ixを加えると、ほとんど全てをLINQで表現出来るようになりますが、やりすぎて解読困難に陥ったりしがちなのには少し注意を。複雑になるようならベタベタ書かずに、一定の塊にしたものを別メソッドに分ければいいし、分けた先では、メソッドを組み合わせるよりも、yield returnで書いたほうが素直に表現出来るかもしれません。

適切なバランス感覚を持って、よきLINQ生活を!

linq.js

LINQ to ObjectsのJavaScript実装であるlinq.jsにも、標準クエリ演算子の他に(作者の私の趣味で)大量のメソッドが仕込んであるので、せっかくなのでそれの解説も。標準クエリ演算子にあるものは省きます(挙動は同一なので)。また、C#でIEqualityComparer<T>を受け取るオーバーロードは、全てキーセレクター関数のオーバーロードに置き換えられています。

一行サンプルと実行はlinq.js Referenceのほうをどうぞ。

Alternate 値の間にセパレーターを織り込む、HaskellのIntersperseと同じ
BufferWithCount IxのBufferと同じ、次のアップデートでBufferに改称予定
CascadeBreadthFirst 幅優先探索でシーケンスを再帰的に平らにする、IxのExpandと同じ
CascadeDepthFirst 深さ優先探索でシーケンスを再帰的に平らにする
Catch IxのCatchと同じ
Choice 引数の配列、もしくは可変長引数をランダムに無限に列挙する生成子
Cycle 引数の配列、もしくは可変長引数を無限に繰り返す生成子
Do IxのDoと同じ
Finally IxのFinallyと同じ
Flatten ネストされた配列を平らにする
Force シーケンスを列挙する
ForEach IxのForEachと同じ
From 配列やDOMなど長さを持つオブジェクトをEnumerableに変換、linq.jsの要の生成子
Generate ファクトリ関数を毎回実行して値を作る無限シーケンス生成子、IxのGenerateとは違う(IxのGenerateはUnfoldで代用可)
IndexOf 指定した値を含む最初のインデックス値を返す
Insert 指定したインデックスの箇所に値を挿入、Insert(0, value)とすればIxのStartWithと同じ
LastIndexOf 指定した値を含む最後のインデックス値を返す
Let 自分自身を引数に渡し、一時変数を使わず自分自身に変化を加えられる
Matches 正規表現のマッチ結果をシーケンスとして列挙する生成子
MaxBy IxのMaxByと同じ
MemoizeAll IxのMemoizeと同じ、次のアップデートでMemoizeに改称予定
MinBy IxのMinByと同じ
Pairwise 隣り合う要素とのペアを列挙
PartitionBy キーで指定した同じ値が続いているものをグループ化する
RangeDown 指定個数のマイナス方向数値シーケンス生成子
RangeTo 指定した値まで(プラス方向、マイナス方向)の数値シーケンス生成子
RepeatWithFinalize 単一要素の無限リピート、列挙完了時にその要素を受け取る指定した関数を実行
Return IxのReturnと同じ
Scan IxのScanと同じ
Share IxのShareと同じ
Shuffle シーケンスをランダム順に列挙する
TakeExceptLast IxのSkipLastと同じ
TakeFromLast IxのTakeLastと同じ
ToInfinity 無限大までの数値シーケンス生成子
ToJSON シーケンスをJSON文字列に変換(組み込みのJSON関数のあるブラウザかjson2.jsの読み込みが必要)
ToNegativeInfinity マイナス無限大までの数値シーケンス生成子
ToObject JSのオブジェクトに変換
ToString 文字列として値を連結
Trace console.logで値をモニタ
Unfold Aggregateの逆、関数を連続適用する無限シーケンス生成子
Write document.writelnで値を出力
WriteLine document.writeln + <br />で値を出力
TojQuery シーケンスをjQueryオブジェクトに変換
toEnumerable jQueryの選択している複数の要素を単一要素のjQueryオブジェクトにしてEnumerableへ変換
ToObservable 引数のSchduler上で(デフォルトはCurrentThread)Observableへ変換
ToEnumerable Cold ObservableのみEnumerableへ変換

Ixと被るものもあれば、そうでもないものも。ToStringなどは分かりやすく便利でよく使うのではかと。ToJSONもいいですね。Fromは拡張メソッドのない/prototype汚染をしないための、JavaScriptだけのためのメソッド。Matchesは地味に便利です、JSの正規表現は使いやすいようでいて、マッチの列挙はかなり面倒くさいので、そこを解消してくれます。linq.jsは移植しただけ、ではあるんですが、同時に移植しただけではなくて、JavaScriptでLINQはどうあるべきか、どうあると便利なのか、という考えに基づいて調整されています。

JavaScriptにはyield returnがないので(Firefoxにはyieldありますが)、シーケンスは全て演算子の組み合わせだけで表現できなければならない。というのが、手厚くメソッドを用意している理由でもあります。これだけあれば何だって作れるでしょう、きっと多分恐らく。

まとめ

これで今日からLINQ to Objectsマスター。Rx版もそのうち書きます(以前にReactive Extensions入門 + メソッド早見解説表を書きましたが、今は結構変わってしまいましたからね)。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive