Metro UIのボタンエフェクトをWindows Phoneに適用する

Windows Phoneで遊び始めている。
Windows Phone自体もそうだが、WPFも殆ど触ったことがないので、とても苦しいw
取り合えず、簡単なアプリを実際に公開してみて感触をつかみつつ、WPF理解への足掛かりにしたいなと思っている。

で、早速製作中なのだが、Metro UI(名称がぽしゃったので、何と呼べばいいのか困るなぁ)に使用するユーザーインターフェイスのボタン、

WP8MetroUI

のマウスカーソルのあたりをタップした時に、ボタンの隅が押されて変形したような挙動でフィードバックがある、アレをやりたいと思ったのだが、どうも簡単に出来ないようだ。
で、WPFの変形の基礎とか、コントロールのカスタマイズの方法など、色々調べて以下のコードを書いた。

(ここまで紆余曲折の末、約2日 orz 直前にGeometry/Path/RenderTargetBitmapだけいじっていたのが幸いした。でないと、座標がdoubleというだけでも悶絶していたかもしれない…)

public sealed class TiltBehavior : Behavior<UIElement>
{
    private PlaneProjection projection_;

<pre><code>public TiltBehavior()
{
    this.Depth = 30.0;
    this.Tracking = true;
}

public double Depth
{
    get;
    set;
}

public bool Tracking
{
    get;
    set;
}

protected override void OnAttached()
{
    this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
    this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
    this.AssociatedObject.LostMouseCapture += AssociatedObject_LostMouseCapture;

    if (this.Tracking == true)
    {
        this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
    }
}

protected override void OnDetaching()
{
    this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
    this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
    this.AssociatedObject.LostMouseCapture -= AssociatedObject_LostMouseCapture;

    if (this.Tracking == true)
    {
        this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
    }
}

private static void Apply(Size size, Point point, PlaneProjection projection, double depth)
{
    // コントロールのサイズからノーマライズした割合を得る
    var normalizePoint = new Point(
        point.X / size.Width,
        point.Y / size.Height);

    // 0~1の範囲外を切り捨てる
    var satulatePoint = new Point(
        (normalizePoint.X &amp;gt; 1.0) ? 1.0 : ((normalizePoint.X &amp;lt; 0.0) ? 0.0 : normalizePoint.X),
        (normalizePoint.Y &amp;gt; 1.0) ? 1.0 : ((normalizePoint.Y &amp;lt; 0.0) ? 0.0 : normalizePoint.Y));

    // 中心位置からの割合を得る
    var originPoint = new Point(
        satulatePoint.X * 2.0 - 1.0,
        satulatePoint.Y * 2.0 - 1.0);

    // 絶対位置
    var absolutePoint = new Point(
        Math.Abs(originPoint.X),
        Math.Abs(originPoint.Y));

    // 中心からの位置関係
    var directionX = originPoint.X &amp;gt;= 0.0;
    var directionY = originPoint.Y &amp;gt;= 0.0;

    // タップされた位置に応じて、回転軸位置を固定する(0又は1)
    projection.CenterOfRotationX = directionX ? 0.0 : 1.0;
    projection.CenterOfRotationY = directionY ? 0.0 : 1.0;

    // 辺ではなく、中心をタップした場合にも、フィードバックを得る
    // (辺をタップした場合は0に近づく事で影響を避ける)
    var distance = (absolutePoint.X &amp;gt; absolutePoint.Y) ? absolutePoint.X : absolutePoint.Y;
        projection.GlobalOffsetZ =
        (1.0 - distance) *
        0.5 *       // 中心位置でのZ座標
        (-depth);

    // Rotationは角度なので、計算して算出
    projection.RotationY =
        Math.Atan2(depth * (0.0 - originPoint.X) * 0.5, size.Width) /       // 0.5はGlobalOffsetZに含まれているので
        (Math.PI / 180.0);
    projection.RotationX =
        Math.Atan2(depth * originPoint.Y * 0.5, size.Height) /      // 0.5はGlobalOffsetZに含まれているので
        (Math.PI / 180.0);
}

private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (this.AssociatedObject.Projection == null)
    {
        this.AssociatedObject.CaptureMouse();

        projection_ = new PlaneProjection();
        this.AssociatedObject.Projection = projection_;

        var size = this.AssociatedObject.RenderSize;
        if ((size.Width * size.Height) &amp;gt; 0)
        {
            var point = e.GetPosition(this.AssociatedObject);
            Apply(size, point, projection_, this.Depth);
        }
    }
}

private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
    if (projection_ != null)
    {
        var size = this.AssociatedObject.RenderSize;
        if ((size.Width * size.Height) &amp;gt; 0)
        {
            var point = e.GetPosition(this.AssociatedObject);
            Apply(size, point, projection_, this.Depth);
        }
    }
}

private void Uncapture()
{
    if (object.ReferenceEquals(this.AssociatedObject.Projection, projection_) == true)
    {
        this.AssociatedObject.Projection = null;
        projection_ = null;

        this.AssociatedObject.ReleaseMouseCapture();
    }
}

private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    Uncapture();
}

private void AssociatedObject_LostMouseCapture(object sender, MouseEventArgs e)
{
    Uncapture();
}
</code></pre>

}

何しろWPFは完全に初心者なので、変なことをやっていたり、思想から外れる設計なのかもしれないのであしからず。これはUIElementクラスに適用できるビヘイビアクラスで、プロジェクトに入れておいて、ページのXAMLで以下のような感じで使う。

&lt;ListBox x:Name=&quot;MainListBox&quot; Margin=&quot;0,0,0,0&quot; Padding=&quot;0,0,0,0&quot;&gt;
    &lt;ListBox.ItemTemplate&gt;
        &lt;DataTemplate&gt;
            &lt;StackPanel Margin=&quot;8,0,8,8&quot;&gt;
                &lt;TextBlock Margin=&quot;0,0,0,0&quot; Padding=&quot;0,0,0,0&quot; Text=&quot;{Binding Name}&quot; TextWrapping=&quot;Wrap&quot; Style=&quot;{StaticResource PhoneTextSubtleStyle}&quot;/&gt;
                &lt;TextBlock Margin=&quot;8,8,0,8&quot; Padding=&quot;0,0,0,0&quot; Text=&quot;{Binding Description}&quot; TextWrapping=&quot;Wrap&quot; Style=&quot;{StaticResource PhoneTextSubtleStyle}&quot;/&gt;
                &lt;i:Interaction.Behaviors&gt;   &lt;!-- ココ --&gt;
                    &lt;Behaviors:TiltBehavior /&gt;
                &lt;/i:Interaction.Behaviors&gt;
            &lt;/StackPanel&gt;
        &lt;/DataTemplate&gt;
    &lt;/ListBox.ItemTemplate&gt;
&lt;/ListBox&gt;

ListBoxにコレクションをバインディングし、その要素毎にStackPanelで表示する。そのStackPanelにビヘイビアの指定を行うと、対応するクラスのビヘイビアが呼び出される。名前空間「i」は、System.Windows.Interactivityで、これはWindows Phone以外ではアセンブリが違うかもしれないが存在すると思う。最初にxmlns:iで宣言しておくこと。

なお、Buttonに適用するとうまく動かない。何故かは、これから悩むところ (^^;; StackPanelのクリックを検出する方向で逃げたほうがいいのか、Buttonで真面目にやるほうがいいのか、それすら分からないのが困ったものだ…

コーディングガイドラインの車輪を再発明

正直、「好きだね、決めるのが」というところなのだが、コーディングガイドラインについての定義が多いなあと思う。
先日、CodeProjectで、またもやその一つが投稿され、コメントが炎上(?)していた。

Some practices to write better C#/.NET code

この中に何点か、自分的に納得いかないプラクティスがある。

  • Capitalization Conventions – プロパティはともかく、フィールドはプライベートもPascalなのか?自分的にはこうだ:moneyRemains_ – これは、ANSI C/C++において、安全とみなせるシンボル表記だ。但し、アンダースコアが後ろに続く表記はあまり見たことが無いかもしれない。自分のように、C++、C#のどちらも頻繁に書く場合、どちらでも通用する書き方にしたい。但し、それはシンボルが公開インターフェイスでなければ、の話だ。この記事ではないが、進行中のプロジェクトでは、readonlyフィールドが全て大文字という縛りがあって、かなり違和感がある。ここだけ単語の区切りが分かりにくいので、アンダースコアで区切ることになる(DIEING_DICTIONARYのような)。すると、確かに目立つは目立つが、まるでconstかと主張しているようだ(constではなくreadonlyなのだが)。どうも、readonlyインスタンスとconstの決定的な違いを分からず決めたように感じる。
  • DO declare all member variables at the top of a class, with static variables at the very top. – たのむからプロパティをコンストラクタより上に持ってこないでくれ。フィールドとプロパティを同一のように扱うと罠にはまる事があるから、見てすぐにわかるようにしておきたい。
  • DO NOT suffix enum names with Enum – これは同意だが、できれば列挙型名は複数形にして欲しいな。列挙型を定義してTlbExpでタイプライブラリを出力、C++側でインポートした時とか、不自由しなくなる。まれなケースだけど、まれなケースの時だけルールを変えるぐらいなら、複数形にする事に負担があるわけじゃないからやればいい。
  • Avoid Obsolete Comments – これはかなり炎上していたな。前提としては、コメントを「書かなくても済むようにする(シンプルで意味が明瞭な実装を心がける)」ということに賛成だが、不可避な場合がある。結局、突き詰めていくと、コーディングの問題じゃなくて、大枠の設計(ユーセージモデルとか)が問題で複雑化してしまっていたりするので、どうしてもコードが複雑にならざるを得ない事がある。そのような場合に、将来自分でこのコードを見たときに、昔自分が何を考えてこのように実装したかを書いておくためにコメントを使っている。これを書いておかないと(自分でも)理解不能ではないだろうかと思うような場所で、かつ改善できないような痛い場所だ。しかし、出来れば設計からやり直させたいね。
  • Avoid Multiple Exit Points – これは絶対拒否だ。一体これをやりたがるのは何故だ?CPUにはプログラムカウンタ(インストラクションポインタ)とスタックというものがあり、これらの情報で一種の仮想的な「ハードウェアステートマシン」を実現しているのだ。条件分岐のブロック内に居るという「事実」が、この情報だけで表現されているのに、何故手でステートを管理したがるのか。ステート変数を導入すれば、それがいつどこでどのように変更され、どのような場所や場合においても正しくふるまっているかどうかを管理しなければならなくなる。C++でRAIIが当たり前に使われているのは、こうしたステート変数を避けるためだ。これ以上、処理の必要がない分岐ルートでは、速やかにメソッドから退出(return)する。そうすることで、余計な分岐ルートを削減できる。分岐ルートを削減できれば、パターン網羅のためのテストコードも少なくて済む。メソッドが下に落ちていくほどルートはふるいにかけられ、終盤ではそこに居るための「暗黙条件」は絞られるはずだ。結果として、コードはよりシンプルになる。

思うに、こういう事は意外かもしれないが、「ルールを決めて徹底させる」事が諸悪の元凶に思う。たとえば、重厚な開発プロセスを導入する事は、そこから緩慢な死へと向かい、より良い未来に前進することはない。プロセスを定義するという事は、「考えなくても出来る」ことを目指すため、例外なく「思考停止」し、本当は何が問題でどうしようとしていたのかが置き去りにされてしまう。常に最善とは何かを考えるためには、プロセスは邪魔なのだ。

もっとも、多人数で作業を進めるために、何らかの決まり事は必要となる。重要なのは、それが常に陳腐化する可能性があり、いつでもすぐに見直し、正しい方向に修正しなければならないことをメンバー全員が周知していなければならない事だ。加えて、業務を遂行するために、いつでもルールが変更可能でなければならない。何らかのプロセスを導入していると、「こうするのがルールだから仕方ないよね」というところから始まって、なし崩し的に開発効率が落ちていく。終いには、何が悪くてこうなってしまったのかすら、誰もわからないという寒い状況に陥る。

上記に示したルールは、私が業務を進めるにあたって、C++/C#の両刀使いである前提でのルールも含まれている。CodeProjectの元記事や私のルールを鵜呑みにせず、積極的にルールをぶち壊し、再発明してほしいものだ。

LINQは本当に強力だ (8) 何が並列化されるのか

並列化が簡単に出来る事がLINQのアドバンテージの一つなのだが、AsParallelを付ける際に、クエリのどこがどのように並列化されるのかが見えているだろうか?
AsParallelを付ければ並列化出来るというのは簡単すぎるため、意味のない適用や実害が生じる可能性もある。たとえば、以下のコードを考えてみる。

// 重複しない乱数列を生成する
var rand = new Random();
var captured = new ConcurrentDictionary<int, int>();
var results =
    from index in
        Enumerable.Range(0, 100000)
    let value = rand.Next(200000)
    where captured.TryAdd(value, index) // 辞書に値の追加を試み、成功すればtrue
    select value;

少し脱線するが、ConcurrentDictionaryはDictionaryのスレッドセーフバージョンだ。しかも、ロック競合が起きにくく、かつアトミック操作が可能なメソッドが追加されており、上記のようにLINQでも使いやすい(DictionaryクラスはTryGetValueがoutパラメータを使うので、LINQでは使いにくかった)。

さて、このコードは小さいので並列化にはあまり向いていないが、それは横に置いておき、並列化したいのはどこなのかを考える。
やろうとしていることは、10万回分の乱数を取得し、その中から重複する値を避けた乱数列の取得だ。もちろん、Distinctを使えばいいのだが、それも横に置いておく。
生成した乱数(value)を辞書に追加し、成功すれば(つまり辞書にその値が無く、追加された場合)に、その値がselectで射影される。この、乱数の生成と辞書への追加の試行部分を並列化出来れば、大半の処理が並列化出来たことになる。
クエリ構文で書いていることもあり、AsParallelを挿入すべき場所は一か所しかないが、取りあえず挿入する。

var rand = new Random();
var captured = new ConcurrentDictionary<int, int>();
var results =
    from index in
        Enumerable.Range(0, 100000).
        AsParallel()    // 並列化
    let value = rand.Next(200000)
    where captured.TryAdd(value, index)
    select value;

LINQ(とPLINQ)を書き始めて間もないと、このAsParallelによって何が並列化されるのか、誤解する事が多いようだ。上記の例では、10万までの数値を生成する「Range」が並列化され、それ以降の乱数生成や判定が並列化されることも期待しつつも、本当に並列化されるのか自信が持てないらしい。
(いや、自分も最初はそうだったから、そういうものだと思う :-)

最初に書いた通り、良くも悪くもAsParallelを付けるだけでよいというのが、この誤解の主原因なのだろう。
まず、クエリ構文をメソッド構文にする。

var rand = new Random();
var captured = new ConcurrentDictionary<int, int>();
var results =
    Enumerable.Range(0, 100000).
    AsParallel().
    Select(delegate(index)
        {
            return new
            {
                value = rand.Next(200000),
                index = index
            };
        }).
    Where(delegate(entry)
        {
            return captured.TryAdd(entry.value, entry.index)
        }).
    Select(delegate(entry)
        {
            return entry.value;
        });

意味もなくletを使ってしまったので面倒な事になっているが、前回見せたとおりindexとvalueを射影しているだけだ。肝心のAsParallelは、letを射影するSelectの手前に入っている。Range→AsParallel→Select→Where→Selectと、パイプライン結合されているのが分かる。

そう、パイプライン結合されているのだ、AsParallelも。

パイプライン結合を実現しているのは、IEnumerable<T>インターフェイスだ。クエリの末端(最後のSelect)が列挙されるとき、GetEnumeratorが呼び出される連鎖が起きることを述べた。AsParallelもまた、同じようにGetEnumeratorの呼び出しと、列挙の連鎖が発生する。ということは、AsParallelが動作に影響を与えられるのは、自分よりも上位のRangeだけ、と言う事になる。

え?そうなの?
いやいや、実はこれが判りにくい原因ではないかと思う。AsParallelよりも下位のパイプライン結合が並列化されるのだ。つまり、乱数の生成と辞書への追加が、期待通り並列化される。Rangeによるindexの生成は並列化「されない」。
どうしてこのようになるのだろうか?ここにクエリ構文を使いすぎたり、varを使いすぎたりする弊害がある。上記のコードをばらばらにし、.NET2.0的にしてみる。

IEnumerable<int> results0 =
    Enumerable.Range(0, 100000);

ParallelQuery<int> results1 =
    results0.AsParallel();

ParallelQuery<Tuple<int, int>> results2 =
    results1.Select(delegate(index)
        {
            return Tuple.Create(rand.Next(200000), index);
        });

ParallelQuery<Tuple<int, int>> results3 =
    results2.Where(delegate(entry)
        {
            return captured.TryAdd(entry.Item1, entry.Item2);
        });

ParallelQuery results4 =
    results3.Select(delegate(entry)
        {
            return entry.Item1;
        });

匿名クラスは表現できないので、Tupleに置き換えてある。AsParallelの戻り値の型は、実はIEnumerable<T>ではない。ParallelQuery<T>型なのだ。但し、ParallelQuery<T>はIEnumerable<T>を実装しているので、これを直接foreachなどで列挙することは可能だ。つまり、今まで通りIEnumerable<T>が返されると思っていても、表面上の違いは無いということだ。

しかし、このコードを見れば、なんとなく並列化される範囲が見えてくると思う。ParallelQueryによって管理されているクエリが並列化される。少し不思議なのは、IEnumerable<T>に対してSelectやWhereを呼び出した場合はIEnumerable<T>が返されるのに、ParallelQuery<T>に対してSelectやWhereを呼び出した場合は、ParallelQuery<T>が返されることだ。

これもそれほど大げさな仕掛けではない。IEnumerable<T>の場合は、SelectやWhereといった拡張メソッドは「Enumerable」クラスに定義されている。ParallelQuery<T>に対してSelect・Whereした場合は、「ParallelEnumerable」クラスの拡張メソッドが使用されるのだ。C#のコンパイラが、SelectやWhereメソッドの第一引数の型にもっとも一致する拡張メソッドを自動的に選択するため、この仕掛けが機能する。まるで、メソッドのオーバーライドを実装しているかのようだ。

では、いったいどこで並列実行のからくりが実現されるのだろうか? ParallelQuery(ParallelEnumerable)の実装は複雑だが、基本的な考え方は単純だ。ParallelQuery<T>の最下位でGetEnumeratorが呼び出されたときに、並列化が行われる。

IEnumerable<int> results0 =
    Enumerable.Range(0, 100000);

ParallelQuery<int> results1 =
    results0.AsParallel();

ParallelQuery<Tuple<int, int>> results2 =
    results1.Select(delegate(index)
        {
            return Tuple.Create(rand.Next(200000), index);
        });

ParallelQuery<Tuple<int, int>> results3 =
    results2.Where(delegate(entry)
        {
            return captured.TryAdd(entry.Item1, entry.Item2);
        });

ParallelQuery<int> results4 =
    results3.Select(delegate(entry)
        {
            return entry.Item1;
        });

// ParallelQuery.GetEnumerator()が呼び出される
foreach (var value in results4)
{
    Console.WriteLine(value);
}

最下位でGetEnumeratorが呼び出されると、スレッドプールからいくらかのスレッドを割り当て、各スレッドがParallelEnumerableの拡張メソッドで定義された演算を実行する。この部分は、従来のGetEnumeratorによる結合では実行できない。何故なら、IEnumeratorインターフェイスはマルチスレッドに対応していないからだ。必然的に、パイプラインで並列化演算が可能なのは、それぞれの演算が専用に設計された、ParallelEnumerableに定義された拡張メソッド群だけ、ということになる。

(もちろん、それらの拡張メソッドから呼び出されるデリゲートの実装は、いくらでも拡張可能だ。SelectやWhereのデリゲートの実装は、使う側が記述するのだから。)


AsParallelの実装とか、ParallelQueryEnumeratorの実装に興味がわくかもしれない。この部分はスレッドの割り当てやデータの分散と集約など、実際にはかなり複雑になっていると思われる。

しかし、注目する点はその部分ではなく :-) 、各ワーカースレッドが並列実行している演算の部分だ。Select→Where→Selectの部分が、スレッド毎に分散されている。パイプラインでAsParallelを適用してから、foreachで列挙されるまでに結合された演算子が並列実行される事が分かる。
そして、(当たり前ではあるが)foreachによるGetEnumeratorの呼び出しの後(foreachループ)は、並列化されていない。Console.WriteLineが並列実行されるわけではない事も分かる。ここが並列実行されたとすると、記述したとおりに実行されないわけだから、C#の構文的にも変だ。
また、AsParallelの上位も並列化されない。こちらも、IEnumeratorインターフェイスの構造に阻まれて、並列化させることはできない。

結局のところ、IEnumerable.GetEnumeratorの呼び出しによって、並列化の「壁」が出来上がるわけだ。私はこれを「ゲート」と勝手に呼んでいる。PLINQクエリが入り混じる場合、このゲートを意図的に作ってやることで、並列化されるクエリの範囲を自由にコントロールできる。

IEnumerable.GetEnumeratorを明示的に呼び出させる事が出来れば、このゲートを作ったことになる。つまり:

IEnumerable<int> results0 =
    Enumerable.Range(0, 100000);

ParallelQuery<int> results1 =
    results0.AsParallel();

ParallelQuery<Tuple<int, int>> results2 =
    results1.Select(delegate(index)
        {
            return Tuple.Create(rand.Next(200000), index);
        });

// results2の直後にゲート生成
IEnumerable<Tuple<int, int>> results3 =
    ((IEnumerable<Tuple<int, int>>)results2).Where(delegate(entry)
        {
            return captured.TryAdd(entry.Item1, entry.Item2);
        });

IEnumerable<int> results4 =
    results3.Select(delegate(entry)
        {
            return entry.Item1;
        });

ということだ。こうすれば、results2だけが並列化の対象となる。

で、キャストは面倒であるため、この目的のための「AsEnumerable」という拡張メソッドがある。内部の実装はキャストしているだけだ。PLINQ向けには「AsSequential」もあるが、これはAsEnumerableと全く変わらない。AsParallelの逆演算子のイメージで導入されたのだと思う。

LINQは本当に強力だ (7) 範囲変数と構文の選択

LINQのクエリ構文には、範囲変数と呼ばれている機能がある。
たとえば、文字列で与えられた半径と円周を対にしてみる。

string[] values = new string[] { "234.56", "456.78", "345.67", "123.45" };
IEnumerable<Tuple<double, double>> results =
    from value in values
    select Tuple.Create(double.Parse(value), double.Parse(value) * 2 * Math.PI);

これで正しい結果が得られるのだが、文字列の数値をdoubleにパースする操作は2回行われる。
CLRがとても賢くなったとしたら、double.Parseに副作用が存在しないことをJITで確認して、1回の呼び出しに変換するかもしれないが、あまり期待できない。
そこで、普通ならパースした結果をローカル変数に入れておき、使いまわしたりするのだが、このような目的のために、LINQには「範囲変数」が存在する。

string[] values = new string[] { "234.56", "456.78", "345.67", "123.45" };
IEnumerable<Tuple<double, double>> results =
    from value in values
    let parsed = double.Parse(value)    // 範囲変数parsedに代入
    select Tuple.Create(parsed, parsed * 2 * Math.PI);

これなら確実に1回だけパースを行う。その結果を、範囲変数”parsed”に代入し、後で何度でも使いまわす事が出来る。
そういう訳で、このletは実に使い出がある。というより、これが無いとかなり困ったことになる。
ここで、新たな「let」という予約語(厳密には予約語ではないが、ここでは詳しく述べない)が登場するのだが、最初に見たときは「何でvarにしなかったんだ?」と思った。

もはやvarについて知らない人はいないと思うが、

// 右辺から、文字列の配列って分かるよね?
var values = new string[] { "234.56", "456.78", "345.67", "123.45" };
// 右辺から、Tuple<double, double>の列挙子って分かるよね?
var results =
    from value in values
    let parsed = double.Parse(value)
    select Tuple.Create(parsed, parsed * 2 * Math.PI);

というように書き直せる。右辺の結果の型が明確であれば、左辺にわざわざ型名を書かなくても良いでしょうというのが一点。もう一点は連載でもちらりと述べたが、匿名クラスをローカル変数に保持するのに、型名が書けないからという理由だ。

さて、やっとvarについて書けた。以後は注目すべき点がない場合はvarで書く :-)
varの意味づけがイメージ出来るように、実際に匿名クラスを使ってみようか。

var values = new string[] { "234.56", "456.78", "345.67", "123.45" };
// 型名が無いから、書きようがない
var results =
    from value in values
    let parsed = double.Parse(value)
    select new  // 匿名クラスだから、型名はない
    {
        R = parsed,
        Length = parsed * 2 * Math.PI
    };

上記の「results」は、varでしか宣言出来ない。故にvarという予約語が必要になった訳だ。
で、範囲変数であるletも事情は同じだ。場合によっては、型名が書けない事がある。それならば、letという予約語を作るよりもvarが使えたほうが、覚える予約語が減って良いのではないか?

var values = new string[] { "234.56", "456.78", "345.67", "123.45" };
var results =
    from value in values
    var parsed = double.Parse(value)    // varでいいじゃん?(もちろんNG)
    select new
    {
        R = parsed,
        Length = parsed * 2 * Math.PI
    };

何故letが導入されたのかは明らかではないが、私が感じた理由を示そうと思う。
まず、上のコードのアウトプットを少し増やす。

var values = new string[] { "234.56", "456.78", "345.67", "123.45" };
var rand = new Random();
var results =
    from value in values
    let parsed = double.Parse(value)
    let randomValue = rand.Next((int)parsed)    // 乱数を生成する
    select new
    {
        R = parsed,
        Length = parsed * 2 * Math.PI,
        Rand = randomValue
    };

ちょっと強引な例だが、パースした数値の範囲の乱数を加えた。見ての通り、あらかじめ代入した範囲変数parsedを使って、別の範囲変数を生成することも出来る。この辺りは、通常のローカル変数の感覚と全く変わらない。次のようなコードを書くまでは:

var values = new string[] { "234.56", "456.78", "345.67", "123.45" };
var rand = new Random();
var results =
    from value in values
    let parsed = double.Parse(value)
    let randomValue = rand.Next((int)parsed)
    orderby parsed  // ちょっとソートしてみようか。
    select new
    {
        R = parsed,
        Length = parsed * 2 * Math.PI,
        Rand = randomValue
    };

私は、上記のselectまで書いたところで、「凍り付いた」。意味が分かるだろうか?
parsedの値で昇順にソートするために、orderbyを書いた。ここまでは良いのだが、その下のselectが一体どうなってしまうのか、非常に不安になった。もちろん、parsedは昇順に並び替えられた順序でやってくる(select句はSelect拡張メソッドに対応している。OrderByの列挙子で順番に値が渡される事を思い出そう)。
では、randomValueはどうなのか?

上のクエリ構文を見ていると、randomValueはorderbyとは関係がないように見える。実際、その下のselect句でrandomValueをそのまま使っている。と言うことは?ソートされるのはparsedの値だけで、randomValueはソートされずに渡されるのか?すると、組み合わせるべき結果がメチャメチャになってしまうのではないか?
(いやいやいや、GetEnumeratorで渡されるというインフラを無視して、ワープしたかのように別のルートで値を渡すなど不可能、不可能なハズだ…?)

もし、let句が無く、varと「書けた」としたら、益々この罠に掛かったことだろう。

翌々書いておく。「ローカル変数」と「範囲変数」は異なるのだ。異なるのだ。異なるのだ。
上記のクエリ構文をメソッド構文に置き換える事が出来るだろうか? この罠に掛かってからは、メソッド構文のイメージが全く掴めなかった。結局、ILSpyでコードを確認して、以下のように変換されることが分かった。

var values = new string[] { "234.56", "456.78", "345.67", "123.45" };
var rand = new Random();
var results =
    values.
    Select(delegate(value)
        {
            var parsed = double.Parse(value);
            return new  // 不可視の匿名クラス
            {
                parsed = parsed,    // let parsedに対応
                randomValue = rand.Next((int)parsed)    // let randomValueに対応
            };
        }).
    OrderBy(delegate(entry)
        {
            return entry.parsed;
        }).
    Select(delegate(entry)
        {
            return new
            {
                R = entry.parsed,
                Length = entry.parsed * 2 * Math.PI,
                Rand = entry.randomValue
            };
        });

letのあるべき部分に、上記のような不可視の匿名クラスが作られ、Selectで一旦射影される。つまり、parsedとrandomValueはその時点の値がセットとして扱われる。だから、直後のOrderByでソートされても、parsedとrandomValueが誤った組み合わせになる事は無い。最後のSelectまで、この組み合わせは維持される。

letとletの間にwhereを入れたりした場合でも、新たな不可視の匿名クラスが作られ、Selectで射影される。分離されたletが存在するだけ、匿名クラスが増えることになる。これはつまり、分離されたletが沢山あるほど、匿名クラスの種類が膨れ上がると言う事だ。まあ、それはまだいい(コンパイラが勝手にやっていることだから)が、その度にnewされるというのは気持ちの良いことではない。この匿名クラスの生成とnewはかなりパターン化していると思われるので、ひょっとするとJITは何か工夫するかもしれない。

実際、範囲変数を正しく振る舞わせる為には、上記のようにするしかないだろう。で、メソッド構文が最も正確にLINQを表現できるとしても、上記のようなコードを書くのは面倒だ。やはりletが使えるクエリ構文が手放せない。私はいつも直感で書くのであまり注意していなかったが、自分でパターンを考えてみると、基本はクエリ構文で書き、letが不要な場合にはメソッド構文を選択しているようだ。

しかし、クエリ構文で書くデメリットもある。以前に述べた拡張メソッドとの相性の問題もあるが、クエリに問題があった場合に追跡が面倒だ。メソッド構文で書いていると、適当な場所でパイプラインをぶった切って、ToList()をかますことで、そこに流れたデータを直接デバッガで可視化出来る(データ量が多いとこの方法は使えないが、デバッグ時だから与えるデータを工夫すればいい)。

クエリ構文だと「ぶった切る」事が出来ない。letを使っているとクエリが複雑化しやすい上にこの制限があるのが辛い。
将来のVisual Studioでパイプラインの流れを可視化出来る何かが追加されると、非常に強力なのだが…
#使った事は無いが、Resharperはクエリ構文をリファクタリングで分割出来るようだ。さすがに強力。

LINQは本当に強力だ (6) TextFieldContext

抽象的な話ばかり続いたので、今回は、実用的な例を示そう。
.NETでCSVファイルを読み取るとき、まさか自分でパースしたりしていないと思うが、知っていると便利なクラスが「VB.NET」のライブラリに存在する。TextFieldParserクラスだ。VB向けの実装の割には、Streamからの読み取りに対応しているなど、割としっかり作ってある。
今回はこのクラスをLINQで「楽に」使えるようにする。

public static class TextField
{
    // 指定されたCSVファイルへのコンテキストを生成する
    public static IEnumerable<string[]> Context(
        string path, string separator = ",", Encoding encoding = null)
    {
        using (Stream stream =
            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            using (TextFieldParser parser =
                new TextFieldParser(stream, encoding ?? Encoding.UTF8, true, false))
            {
                parser.TextFieldType = FieldType.Delimited;
                parser.Delimiters = new[] { separator };
                parser.HasFieldsEnclosedInQuotes = true;
                parser.TrimWhiteSpace = true;
                while (parser.EndOfData == false)
                {
                    string[] fields = parser.ReadFields();
                    yield return fields;
                }
            }
        }
    }
}

このクラスのアイデアは、LINQ to Entitiesだ。Contextメソッドで一旦「コンテキスト」を作ってしまえば、面倒な事を一切考えなくても読み取りが可能になる。これでロジックから解放だ。
郵便局 郵便番号データ(全国)を使う。

static void Main(string[] args)
{
    // 郵便番号データのコンテキストを生成
    IEnumerable<string[]> context = TextField.Context(
        @"KEN_ALL.CSV", ",", Encoding.GetEncoding(932));

    // (クエリは遅延実行なので、以下の式ではまだ処理を開始していない)
    IEnumerable<string> results =
        // コンテキストからフィールド群を取得
        from fields in
            context.
            AsParallel() // 並列化
        // 郵便番号データの住所部分に「字」と「条」が含まれている行を抽出し、
        where fields[8].Contains("字") || fields[8].Contains("条")
        // 郵便番号の降順でソートし、
        orderby fields[2] descending
        // 文字列形式にフォーマットする
        select string.Format("〒{0} {1}{2}{3}", fields[2], fields[6], fields[7], fields[8]);

    // 取りあえず、結果をコンソールに出してみた(ここでGetEnumeratorが呼ばれて実行が開始される)
    foreach (string result in results)
    {
        Console.WriteLine(result);
    }
}

データ量が少ないので、並列化の恩恵はあまりないが、AsParallelしてみた。AsParallelを付けたり外したりしてみて、タスクマネージャのCPUリソースの具合を確認して見ると良い。

前回、LINQのパイプライン実行について説明したが、上記のコードの良い点は、コンテキストからデータが流れるようにやってきて、クエリで加工されて結果が出力される(コンソールに表示される)というところだ。実は、orderby句(OrderByDescending拡張メソッド)は、内部でバッファリングを行っているので、件数に応じてメモリ使用量が増加してしまうが、orderbyが無ければゴミをGCが回収するため、メモリ使用量が増え続けてしまう事はない。
(orderbyはソートを行うのだが、ソートを行うためにはすべてのデータが揃わなければならない。いくらパイプライン実行でも、不可能なことはある。その代わり、AsParallelしていれば、スレッド数に応じてソートが高速化される。)

さあ、仕上げだ。目の前にCSVファイルの山があったとしよう。

static void Main(string[] args)
{
    IEnumerable<string> results =
        from path in
            Directory.GetFiles("CSV_FILES", "*.csv", SearchOption.AllDirectories).
            AsParallel()
        from fields in
            TextField.Context(path, ",", Encoding.GetEncoding(932))
        where fields[8].Contains("字") || fields[8].Contains("条")
        orderby fields[2] descending
        select string.Format("〒{0} {1}{2}{3}", fields[2], fields[6], fields[7], fields[8]);

    foreach (string result in results)
    {
        Console.WriteLine(result);
    }
}

涙が出るほど簡単で、しかも速い。鳥肌が立つね :-)

LINQは本当に強力だ (5) クエリ構文とメソッド構文

アルゴリズムを記述する拡張メソッドは色々書いてみただろうか?

拡張メソッドを書きたくなる場合を考えると、結局のところは、いかにループロジックを書かなくて済むように出来るか?と言う所から始まるように思う。

他の例として:

  • 連続する隣り合う要素をペアにして列挙
    → [A, B, C, D, E] を [{A, B}, {B, C}, {C, D}, {D, E}] で列挙できるようにする。
    ループロジックで書くと、最後の要素を覚えておいて、次回に利用できるようにするとか、ループ回数を-1するだとか、簡単な事なのに泥臭いコードを書く必要がある。
  • STLのsort/uniqueでDistinctのような事をするのと同様の操作
    →LINQにはもちろんDistinct拡張メソッドが存在する。しかし、stableであろうとするからだろうか、異様に遅い。そこで、OrderByでソートしておいて(もちろん、PLINQで)、Unique拡張メソッドを用意し、連続する同じ要素を無視したシーケンスを返すことで、高速に一意化する事が出来る。この、連続する要素を無視するというアルゴリズムが、ループロジック的にやはり汚くなる。だから、拡張メソッド内に押し込めて、そういう事を考えなくても済むようにするわけだ。

ここまでで見てきたように、LINQには「クエリ構文」と「メソッド構文」があり、クエリ構文で記述出来ることは全てメソッド構文で記述可能で、結局のところ、LINQを支えているのは大量の拡張メソッド群となっている。

int[] datas = new int[] { 1, 2, 5, 3, 9, 7, 4 };

// クエリ構文 #1
IEnumerable<string> resultByQuery1 =
    from data in datas
    select data.ToString();

// メソッド構文 #1
IEnumerable<string> resultByMethod1 =
    datas.
    Select(delegate(int data) { return data.ToString(); });

// クエリ構文 #2
IEnumerable<string> resultByQuery2 =
    from data in datas
    orderby data descending
    select data.ToString();

// メソッド構文 #2
IEnumerable<string> resultByMethod2 =
    datas.
    OrderByDescending(delegate(int data) { return data; }).
    Select(delegate(int data) { return data.ToString(); });

// クエリ構文 #3
string[] resultByQuery3 =
    (from data in datas
     orderby data descending
     select data.ToString()).
    ToArray();

// メソッド構文 #3
string[] resultByMethod3 =
    datas.
    OrderByDescending(delegate(int data) { return data; }).
    Select(delegate(int data) { return data.ToString(); }).
    ToArray();

LINQ入門のような記事は、大方クエリ構文で紹介される。つまるところ、SQL構文がネイティブにC#でサポートされたかのように使えますよ、と。しかし、LINQのクエリ構文は、いわゆる「構文糖」なので、上記の例で見せたように、コンパイラはメソッド構文に置き換えた状態でコンパイルを行う。このメソッド構文こそが、LINQの真の姿ということだ。

最後の例では、ToArray拡張メソッドの違和感がよく表れている。メソッド構文では綺麗につながって記述されているが、クエリ構文ではfrom句からselect句まで括弧でくくり、その結果をToArrayしなければならない。括弧を使わないと、select句の一部と見なされ、違ったコードとなってしまう。
そして、メソッド構文で考えると、LINQのパイプライン実行がどのようなものかも見えてくる。

// 一行で記述
string[] resultByOne =
    datas.
    OrderByDescending(delegate(int data) { return data; }).
    Select(delegate(int data) { return data.ToString(); }).
    ToArray();

// 分割して記述
IEnumerable<int> resultStep1 =
    datas.
    OrderByDescending(delegate(int data) { return data; });

IEnumerable<string> resultStep2 =
    resultStep1.
    Select(delegate(int data) { return data.ToString(); });

string[] resultStep3 =
    resultStep2.
    ToArray();

一行で全て記述した場合と、分割して記述した場合で、(バイナリは多少変わるが)コードの効率は全く変わらない(理由は後述)。その上で、分割したそれぞれの式の結果型を見ると、IEnumerable<T>となっていることが分かる。LINQの拡張メソッドの戻り値は、殆どが列挙子で返されるようになっている。ちなみに、ToArrayで返される配列も、IEnumerable<T>を実装しているため、配列に対して更にLINQの拡張メソッドを使用する事が出来る(元々、datasは配列だ)。

この、「列挙子」がパイプライン実行を担っていて、クエリの遅延実行を可能にする。遅延実行とは何だろうか?
唐突だが、ToArrayの中身について考えてみる。ToArrayは列挙子の「遅延実行」が行われず、その場で評価される。

// ToArrayの疑似コード
public static T[] ToArray<T>(this IEnumerable<T> enumerable)
{
    List<T> results = new List<T>();
    foreach (T value in enumerable)
    {
        results.Add(value);
    }
    return results.ToArray();
}

引数で与えられた列挙子は、要素数が分からないため、List<T>を使って要素値を移し替えた(コピー)あと、配列に変換している(Listの使い方としては例が悪いが、趣旨とはずれるので勘弁)。
foreachは、実際には以下のように変換出来る。

public static T[] ToArray<T>(this IEnumerable<T> enumerable)
{
    List<T> results = new List<T>();

    // foreach (T value in enumerable)
    IEnumerator<T> enumerator = enumerable.GetEnumerator();
    while (enumerator.MoveNext() == true)
    {
        T value = enumerator.Current;
        results.Add(value);
    }
    return results.ToArray();
}

例外に対処するコードは省いた。

IEnumerable<T>から、GetEnumeratorメソッドで列挙子本体(IEnumerator<T>)を取得し、これで列挙を実行するループを形成している。resultStep2.ToArray()とした場合、resultStep2.GetEnumerator()を呼び出している事になる。resultStep2はSelectが返しているのだが、Select内部の実装はどうなっているだろうか?

// Selectの疑似コード
public static IEnumerable<U> Select<T, U>(this IEnumerable<T> enumerable, Func<T, U> predict)
{
    // foreach (T value in enumerable)
    IEnumerator<T> enumerator = enumerable.GetEnumerator();
    while (enumerator.MoveNext() == true)
    {
        T value = enumerator.Current;
        U result = predict(value);
        yield return result;
    }
}

ををを、こりゃいかん。yield returnを説明で使ってしまった。これは以下のように展開される(うぅ、面倒だ)。

public static IEnumerable<U> Select<T, U>(this IEnumerable<T> enumerable, Func<T, U> predict)
{
    // ※1
    return new SelectEnumerable<T, U>(enumerable, predict);
}

private sealed class SelectEnumerable<T, U> : IEnumerable<U>
{
    private readonly IEnumerable<T> enumerable_;
    private readonly Func<T, U> predict_;

    public SelectEnumerable(IEnumerable<T> enumerable, Func<T, U> predict)
    {
        enumerble_ = enumerable;
        predict_ = predict;
    }

    public IEnumerator<U> GetEnumerator()
    {
        // ※2
        return new SelectEnumerator<T, U>(enumerator_.GetEnumerator(), predict_);
    }
}

private sealed class SelectEnumerator<T, U> : IEnumerator<U>
{
    private readonly IEnumerator<T> enumerator_;
    private readonly Func<T, U> predict_;

    public SelectEnumerator(IEnumerator<T> enumerator, Func<T, U> predict)
    {
        enumerator_ = enumerator;
        predict_ = predict;
    }

    public U Current
    {
        get
        {
            return predict_(enumerator_.Current);
        }
    }

    public bool MoveNext()
    {
        return enumerator_.MoveNext();
    }
}

説明に不要なコードは省略している。Selectを呼び出すと、結果としてSelectEnumerableのインスタンスが返される(※1)。この時点ではまだ元の列挙子(Selectの引数。resultStep1)は操作していない。

resultStep2にはSelectEnumerableのインスタンスが入っているので、resultStep2.GetEnumerator()を呼び出すということは、SelectEnumerableのGetEnumerator()を呼び出すことだ(※2)。ここで初めてresultStep1のGetEnumeratorが呼び出され、SelectEnumeratorのインスタンスが生成されて返される。

既に忘れているかもしれないが :-) ToArrayの中身のforeach(そしてそれを展開したwhile文)は、IEnumerator<T>を取得した後、MoveNextを呼び出している。上記のMoveNextを見ると、resultStep1のMoveNextに転送しているだけだ。その後、Currentプロパティを参照する。Currentプロパティの中身は、predictデリゲートを使ってresultStep1のCurrentプロパティの値を変換して返している。変換の内容は?LINQクエリを書いた本人が定義するわけだ。
そして、その結果をList<T>に追加し、whileの先頭に戻り、またMoveNextを呼び出す。後は、上記の動作が繰り返されるわけだ。

ここでは複雑すぎるので示していないが、resultStep1へのMoveNext呼び出しも、Currentプロパティの参照も、同じような流れに従っている。

全体を通して、以下のような構造となる。

これを俯瞰して見ると、ToArrayのforeachによるループ1回で、LINQクエリのパイプラインで接続されたメソッド群を「行ったり来たり」していることが分かると思う。この動作が「パイプライン実行」の正体だ。そして、GetEnumeratorが呼び出されるまでは、接続されたクエリが全く動作していないこともわかる。これが「遅延実行」と呼ばれる動作だ。

一行で書いても分割して書いても効率は変わらないと書いた理由が分かるだろうか?遅延実行をサポートするLINQの拡張メソッドは、結局SelectEnumerableのような「クエリ条件だけ保持して何もしない」クラスのインスタンスを返しているだけだからだ。それを一時変数に入れたところで、動作は全く変わらない。

LINQは本当に強力だ (4) アルゴリズムヘルパーメソッド

列挙子を列挙するときに、現在の位置が知りたい場合がある。頭の中にまだLINQの武器が少ないときは、諦めてfor文に逃げたりする。

int[] data = new int[] { 123, 456, 789 };
List<int> results = new List<int>();
for (int index = 0; index < data.Length; index++)
{
    results.Add(data[index] * (index + 1));
}

LINQを使うなら、こういったループ制御構文を出来るだけ使わないようにする事だ。これが最終的に、ロジック重視の設計からデータドリブンの設計に移行するカギとなる。
インデックスが必要なら、インデックスを列挙させれば良い。

int[] data = new int[] { 123, 456, 789 };
List<int> results =
    (from index in Enumerable.Range(0, data.Length)
     select data[index] * (index + 1)).
     ToList();

for文やforeach文で(ロジック的に)記述してしまうと、その後の応用性が失われてしまう。Enumerable.Rangeでインデックスの列挙子を生成すれば、結果は列挙子となる。上の例では早々にToListしてしまったが、もちろん、ここから続けて別のクエリを書く事が出来るし、そうすることでパイプライン実行が行われる。AsParallelすれば並列実行も出来る。応用性が全く違うというわけだ。

ところで、上の例は元ネタとなるdataが配列であるので、要素数が分かる。だからEnumerable.Rangeでインデックスを生成できるのだが、配列である事を想定しているので詰めが甘い。ここは一つ、任意の列挙子で実現してみよう。
ただ置き換えるのであれば、以下のようになる。

IEnumerable<int> data = new int[] { 123, 456, 789 };
List<int> results =
    (from index in Enumerable.Range(0, data.Count())
     select data.ElementAt(index) * (index + 1)).
     ToList();

配列はIEnumerableを実装しているので、Count拡張メソッドを使って要素数を取得するのは間違っていない。また、Count拡張メソッドの実装は、対象がICollectionを実装していることを検知して、実際に列挙することなく要素数を得る事が出来るので、効率も良い。

しかし、列挙子が配列ではない、任意の列挙子であった場合、しかもICollectionを実装していない場合は、一旦列挙を行って要素数を数え上げる。要素数を取得するだけならこれでも良いが、その直後、LINQクエリ内のElementAtで、また列挙を開始するため、二重に列挙を行っていることになる。

コストについてピンと来ないかもしれないが、仮に列挙子がデータベースクエリを抽象化していると、データベースに対して2回のクエリ実行を行うことになる。ほとんどの場合において、これは許容出来ない(LINQ to SQLやLINQ to Entitiesでこのような事をしないように)。

では、どうやって改善するか。

public static IEnumerable<Tuple<int, T>> Indexing<T>(this IEnumerable<T> enumerable)
{
    int index = 0;
    return from value in enumerable select Tuple.Create(index++, value);
}

またしても、ヘルパー拡張メソッドだ。これを使えば、タプルクラスで作った「インデックス詰め合わせ」に変換できる。

IEnumerable<int> data = new int[] { 123, 456, 789 };
List<int> results =
    (from entry in data.Indexing()
     select entry.Item2 * (entry.Item1 + 1)).
     ToList();

危ういロジック記述に頼ることなく、インデックスを保持する外部変数に頼ることなく、2回列挙することなく、クエリを簡便に記述可能になった。しかも、Indexingメソッドの実装もまたLINQクエリなので、返された列挙子は遅延実行される。つまり、パイプライン実行が可能ということだ。


Windows Formsで、コントロールの階層を親方向に辿りたいと思ったことがあるかも知れない。

List<Control> list = new List<Control>();
Control current = innerMostControl;
while (current != null)
{
    list.Add(current);  // 実際にはリストに溜めるのではなく、ロジックを書いちゃったりするよね
    current = current.Parent;
}

この「while」を止めたいのだ。LINQに慣れてくると、「ループ終了条件」が正しいかどうかを、演算式だけで担保したくなる。whileやforやifによる制御構文があると、ロジックによる分岐が一々大丈夫かどうか確認するのが煩わしいし、ミスも生じやすくなる。「インデックスって0からだっけ、1だっけ?最後は”<"か"<="か?」とか、ifでこっちに行って、elseであっちに行ってとか、非常にうっとおしい。思考の邪魔だ。

このようなループ制御を行っている場合は、うまく考えればクエリ化出来る。もうかなりのLINQクエリを書いているが、どうしてもループ制御構文を使わなければ実現できなかったコードは、ほんの僅かだった。
この親コントロールをたどるwhileループは、要するに親までの一方向リンクをリスト化するということだ。やってみよう。

public static IEnumerable<T> TraverseLink<T>(this T begin, Func<T, T> predict)
{
    T current = begin;
    while (current != null)
    {
        yield return current;
        current = predict(current);
    }
}

このyield構文は、前回のyield構文と書き方は同じだが、メソッドが直接IEnumerableを返してしまう点で、更に強力だ。と、同時に、使い慣れていないと、メソッドが返却しているモノが一体何であるのか、混乱するかもしれない。

さて、この武器を使えば、リンクリストの追跡など、安全でたやすく、シンプルだ。

List<Control> list = innerMostControl.TraverseLink(delegate(control) { return control.Parent; }).ToList();

ええ、と? あ、一行か :-)

もちろん、型がControlである必要はない。リンク先がParentである必要もない。これらを担保するのは、ジェネリック引数とFuncデリゲートだ。つまり、任意のリンクリストをこのTraverseLink拡張メソッドでたどる事が出来る。

LINQは本当に強力だ (3) 拡張メソッドによる拡張

ToHashSet()とか、ToSortedList()とか、作ってみただろうか? :-)

もう一つ小ネタを行ってみよう。

複数の文字列を連結出来たら良いのにと思う事がある。ハードコードするなら、+演算子で繋げれば良いのだが、配列や列挙子だとこうは行かない。で、レベルの低い現場でよく見るのが、forで回しながら+演算子で連結という、眩暈のするコードだ(しかもStringBuilderも使っていない)。

もちろん、拡張メソッドを定義してみる。
StringBuilderを使ってもよい(その方がありがたみがあるかも?)が、忘れがちだが、System.StringにConcat()というメソッドがあり、簡単に実現出来る。

string[] words = new string[] { "ABC", "DEFGH", "IJK" };
string concatted = string.Concat(words);

これが忘れやすい理由の一つは、やはりStringクラスのスタティックメソッドであることではないだろうか。
このぐらい、えいやっと…

public static string Concat(this IEnumerable<string> words)
{
    return string.Concat(words);
}

もちろん、拡張メソッドのクラスが含まれる名前空間が using されていなければならない。例えば、プロジェクトで共通で使用するクラスライブラリに、LINQ向け拡張メソッドを含む名前空間を決めておくというのはどうだろうか?
他にも、文字群を連結して文字列にするというのも考えられる。

public static string Concat(this IEnumerable<char> chars)
{
    return new string(chars)
}

最初の例で示した文字列の連結は、たとえばカンマ区切りで取得したい場合もあるだろう。

public static string Concat(this IEnumerable<string> words, string separator)
{
    return string.Join(separator, words);
}

小ネタばっかりだが、そろそろまとめておく。

  • 列挙子を受け取って何かをするメソッドなら、拡張メソッドを使って定義しておくと、LINQクエリで使いまわしやすい。
  • しかし、同じ名前のオーバーロードを沢山定義すると、ニアミスが発生しやすい事は押さえておく必要がある。

例で挙げたメソッドは、全て”Concat”メソッドで、引数が異なるのでオーバーロードとして成立する(LINQのConcat含めて)。しかし、これが原因で使う際にどのオーバーロードを使用すべきか迷うことがある。

私は、”Contains”メソッドをいくつか定義してみたことがある。LINQのContainsは、追加引数で指定された値が列挙値内に含まれているかどうかをテストする。ここに、System.String.IndexOf()とよく似たContainsを定義してみた。また、列挙値が追加引数群の何れかに一致するかどうかをテストするContainsも作ってみた。

そうすると、当然のことながら、Containsのどの実装が欲しい機能を持っているのか、良く分からなくなってしまう。実際、インテリセンスはこれらのContainsを全て掲示してくれるが、やはりいまいちピンとこない。

問題は、似て異なる機能を持ったメソッドを、同じ名前で定義している事だろう。古典的な問題だが、拡張メソッドについても同じ事が発生するので注意した方が良い。”Concat”も、”ConcatWords”とか、”ConcatToString”のような、具体的な名前を使った方が良いこともある。

この問題はどのように考えれば良いだろうか。
LINQでは、拡張メソッドがあらゆる列挙子で使われることを想定している。例えばConcatの定義、

IEnumerable<T> Concat<T>(this IEnumerable<T> lhs, IEnumerable<T> rhs);

というのは、この拡張メソッドだけでT型列挙子の全てに応用が可能だ。つまり、このConcatだけで様々な用途に使用出来る。それだけ既に抽象化されているわけで、これと似た、しかし異なるメソッドを定義しなければならないとすれば、そもそも元のConcatと用途が異なるかも知れない、という事に注意を払えば良いと思う。


さて、LINQ向けに上記のような「部品」を作っておく事の良さを、もうちょっと別の面で示そうと思う。

今までの例は、拡張メソッド内で、直前の列挙子を評価してしまう。ToSortedDictionaryの場合、メソッド内でforeachで回した時点で列挙を開始する。目的がSortedDictionaryに詰め替えることだからこれで良いのだが、LINQ標準の拡張メソッド(たとえばWhereやOrderBy)のように、実際に列挙されるまで動作を遅延させ、列挙しながら処理を行うためにはどうすれば良いだろうか?

.NET 1.0/1.1では、IEnumerableインターフェイスを実装後、GetEnumerator()メソッドを書かなければならなかった。GetEnumerator()は、列挙子の実体処理を行うクラスのインスタンスを返す。これはIEnumeratorインターフェイスを実装していれば、非公開でも構わない。しかし、IEnumeratorはステートマシンの実装を強要するため、はっきりってこれを書くのは面倒だ。

.NET 2.0になって、画期的な構文「yield」が使えるようになった。詳細は省くが、つまりこれを使って実装すればGetEnumerator()の実装は難しくない。

例えば、何もしない拡張メソッドNop()を実装してみる。

public static IEnumerable<T> Nop<T>(this IEnumerable<T> enumerable)
{
    // 内部的な列挙子を返す
    return new NopEnumerable(enumerable);
}

// T型の何もしない列挙子
private sealed class NopEnumerable<T>
{
    // 元の列挙子
    private readonly IEnumerable<T> enumerable_;

    // コンストラクタ
    public NopEnumerable(IEnumerable<T> enumerable)
    {
        // 保存しておく
        enumerable_ = enumerable;
    }

    // 列挙を実行する
    public IEnumerator<T> GetEnumerator()
    {
        foreach (T value in enumerable_)
        {
            // yield構文を使って、要素を返す(ここでは何もしないでそのまま返す)
            yield return value;
        }
    }

    // 非ジェネリック
    IEnumerator IEnumerable.GetEnumerator()
    {
        // バイパス
        return GetEnumerator();
    }
}

NopEnumerable<T>クラスは、GetEnumerator()が呼び出されるまでは、元の列挙子を一切操作しない。

GetEnumerator()が呼び出されると、最初のforeachで初めて列挙子のGetEnumerator(enumerable.GetEnumerator())が呼び出される。ここで、元の列挙子も動作を開始するわけだ。
その後、foreachで得られた値を、yield return構文で一つずつ返却する。このメソッドのソースコードとバイナリコードはまったく異なり、記述どおりに振る舞うステートマシンとしてコンパイルされる。そのお蔭で、GetEnumeratorの実装は非常に簡単になっている。

このコードをスケルトンとして、GetEnumerator()の実装を肉付けすれば良いだろう。一例として、二重の構造を持った列挙子を、一重の列挙子(つまり普通の列挙子)に変換するメソッドを定義する。

// 二重の列挙子を順番に連結し、一重の列挙子に変換する
public static IEnumerable<T> Unloop<T>(this IEnumerable<IEnumerable<T>> enumerable)
{
    return new UnloopEnumerable(enumerable);
}

private sealed class UnloopEnumerable<T>
{
    private readonly IEnumerable<IEnumerable<T>> enumerable_;

    // コンストラクタ
    public UnloopEnumerable(IEnumerable<IEnumerable<T>> enumerable)
    {
        enumerable_ = enumerable;
    }

    // 列挙を実行する
    public IEnumerator<T> GetEnumerator()
    {
        foreach (IEnumerable<T> outer in enumerable_)
        {
            foreach (T inner in outer)
            {
                yield return inner;
            }
        }
    }

    // 非ジェネリック
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

#ひょっとして、LINQ to Objectに既にあるかも? 折を見て、MSDNライブラリにも目を通すと良い。
#思いつくようなメソッドは標準で提供されている事がある。
#なお、LINQ to Objectの拡張メソッドは、Enumerableクラスに定義されている。

“IEnumerable<IEnumerable<T>>”という型を見て、「えっ、これどういう構造?」と一瞬考えたことは無いだろうか?このUnloopを使えば、ループ構造を簡略化出来る。しかも、このメソッドは実際に列挙されるまで動作が遅延され、中間バッファは全く必要ない。LINQにはぴったりのメソッドだ。

しかしながら、この例で言えば、実はLINQクエリだけでも解決できる。

IEnumerable<T> results =
    from outer in enumerable
    from inner in outer
    select inner;

#対応する拡張メソッドは「SelectMany」だ。

selectの書き方次第では、outerとinnerを使った演算結果を返すとか、応用性も高い。Unloopは単純な例なので仕方がないが、逆に言えば、まずLINQクエリだけで解決出来るか考えて、それから拡張メソッドを実装する、という手順にした方が良いかもしれない。もちろん、メソッドを作れば文字通り「何でもアリ」となるので、究極的にはメソッド実装すれば、難しい問題も切り抜けられるはず。

この辺り、SQL Serverで、まずクエリだけでどうやって書けるか、クエリの書き方次第でどのように高速化出来るか、それがダメなら部分的にユーザー定義関数に逃がすか、と考えるのと同じ方法論であって、実際頭の中では同じ部位が働いている感じがする。
#横に逸れるけど、上記のクエリにAsParalell()とかやりたくなるよね? 迷っているなら、その事も考えておこう :-)
つづく

LINQは本当に強力だ (2) クエリの連結と拡張メソッド

小ネタだ(しかし、重要)。
書いたクエリはすぐには実行されず、列挙を行った時点(おおよそ、IEnumerable<T>.GetEnumerator()を呼び出した時点)で、実際にクエリが評価される。LINQメソッドの中には、その場で評価が実行されるものもある。例えば、ToList()がそうだ。

// 何の抽出かな?
int[] years = new int[] { ... };
IEnumerable<int> query =
    from year in years
    // letはクエリ内の一時変数のようなもの。
    // SQL Serverのクエリなら、同じ式を多量に記述してもクエリオプティマイザが検出して重複を取り除くが、
    // LINQ(LINQ to Object)はそこまでやらないので、letを使うと良い場合がある。
    // (ここではそのような深い意味はない)
    let mod4 = (year % 4) == 0
    let mod100 = (year % 100) == 0
    let mod400 = (year % 400) == 0
    where mod400 || (!mod100 && mod4)
    select year;

// クエリを実行してList<T>に格納する
List<int> leapYears = query.ToList();

ToList()の部分は、以下のような事をしている。

List<int> leapYears = new List<int>();
foreach (int value in query)
{
    leapYears.Add(value);
}

あるいは、もっとこう。

List<int> leapYears = new List<int>(query);

後者ならかなり短く記述出来るわけだが、ToList()を使う利点は、LINQのメソッド連結式の延長上で書けるからだ。例えば、

// 2000年より前のうるう年を昇順にしてList<int>に入れて返す
List<int> results =
    // メソッド連結式(Where()とOrderBy()はデリゲートで評価結果を得る。最後にToList()する)
    query.Where(delegate(value) { return value < 2000; }).
        OrderBy(delegate(value) { return value; }).
        ToList();

もちろん、ToList()のところを、new List<int>(…)と書いたって良いのだが、Visual Studio上でこれを書けば、何を言っているのか分かると思う。要するに「すらすら書ける」ということだ。

さて、「すらすら書く」ためには、LINQの標準メソッド群でも、やや不足を感じることがある。
例えば、ToDictionary()を使えば、Dictionary<T, U>に変換できるのだが、SortedDictionaryが欲しい場合には、ToSortedDictionary()なるものを期待しても、そういうメソッドはない。
某サイトでは、一旦ToDictionary()で辞書を生成した後、SortedDictionaryのコンストラクタに渡して生成する例が掲載されていた。しかし、それではDictionaryは完全に無駄だ。Dictionaryは内部でハッシュコード取得と分散を行っているはずなので、そのコストはドブに捨てる事になる。そして、やはりすらすらとは書けない。

そこで、無いものは作ってしまえ、と。

// staticクラスでなければならない
public static class CollectionExtensions
{
    // staticメソッドの第一引数にthisを付けることで、「拡張メソッド」となる。
    // Func<V, T>はジェネリックデリゲートで、V型引数を取り、T型を返すメソッドを受け入れる
    public static SortedDictionary<T, U> ToSortedDictionary<T, U, V>(
        this IEnumerable<V> enumerable,
        Func<V, T> keySelector, Func<V, U> valueSelector)
    {
        // 入れ物を作って...
        SortedDictionary<T, U> dictionary = new SortedDictionary<T, U>();

        // 列挙しながらセレクタを通してキーと値を取得して格納
        foreach (V entry in enumerable)
        {
            dictionary.Add(keySelector(entry), valueSelector(entry));
        }
        return dictionary;
    }
}

セレクタとして受け入れるデリゲートの部分は、.NET 2.0世代の知識では少し理解が厳しいかもしれない。まず、ToDictionary()がどのように使われるのかを確認しておく。

// 適当な数列
int[] values = new int[] { ...};
// 数列を文字列化したものとペアで保存する
Dictionary<int, string> results =
    // 条件式がなくても全く問題なし
    (from value in values
    // 匿名クラスを使って、一時的にクラスに値を保存する
     select new { Arg1 = value, Arg2 = value.ToString() }).
    // 匿名クラスから値を取り出してDictionaryにする。
    // "entry"は、式内でのみ有効な引数(上の匿名クラスのインスタンスが渡って来る)
    ToDictionary(delegate(entry) { return entry.Arg1; }, delegate(entry) { return entry.Arg2; });

匿名クラスというのは、名前の無い、初期化だけが出来るクラスの事で、new 文の後ろのブロックで読み取り専用フィールドを定義・生成出来る。上記ではArg1とArg2を宣言して、それぞれ値を代入した。
匿名クラスには名前がないので、コード上で型名を書くことが出来ない。しかし、上の式を見れば、巧妙にクラス名を避けている事が分かる(ここから、何故”var”が必要になったかが分かれば、良い洞察だ :-)
そして、今やToSortedDictionary()という武器が手に入ったので、以下のように書けばよい。

SortedDictionary<int, string> results =
    (from value in values
     select new { Arg1 = value, Arg2 = value.ToString() }).
    ToSortedDictionary(delegate(entry) { return entry.Arg1; }, delegate(entry) { return entry.Arg2; });

晴れて、イメージ通りのToSortedDictionary()が得られた。
しかし、私はToDictionary()にもやや納得が行かない部分があった。一旦匿名クラスに値を入れ、その後セレクタを使って再度辞書に入れ直すという部分だ。どうもスマートじゃない。
無い道具は作ってしまえ、と。

// 初めからKeyValuePairでいいじゃん?
public static SortedDictionary<T, U> ToSortedDictionary<T, U>(
    this IEnumerable<KeyValuePair<T, U>> enumerable)
{
    // 入れ物を作って...
    SortedDictionary<T, U> dictionary = new SortedDictionary<T, U>();

    // 列挙しながらそのまま格納
    foreach (KeyValuePair&lt;T, U&gt;  entry in enumerable)
    {
        dictionary.Add(entry.Key, entry.Value);
    }
    return dictionary;
}

そうすると、クエリ側にはKeyValuePairを書かなければならなくなる。これをどう捉えるかだが、直後に辞書に入れると分かっているなら、KeyValuePairで生成した方がすっきりするのが私流。

SortedDictionary<int, string> results =
    (from value in values
     select new KeyValuePair<int, string>(value, value.ToString())).
    ToSortedDictionary();

言わずもがな、ToDictionary()にも同じものが作れるよね?
し・か・も! この拡張メソッドはIEnumerable<KeyValuePair<T, U>>を実装する、全てのクラスで使えるのだ。それはつまり、Dictionary<T, U>とSortedDictionary<T, U>そのものだ。

Dictionary<int, string> hashed = new Dictionary<int, string>();

// .. 辞書にいろいろ追加

// DictionaryをSortedDictionaryに変換
SortedDictionary<int, string> sorted = hashed.ToSortedDictionary();

// SortedDictionaryをDictionaryに変換
Dictionary<int, string> dictionary = sorted.ToDictionary();

もちろん、辞書じゃなくてもOKだ。

// IEnumerable<KeyValuePair<T, U>>を実装していればいいのだから...
List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>();

// ..

// ListをSortedDictionaryに変換
SortedDictionary<int, string> sorted = list.ToSortedDictionary();

// ListをDictionaryに変換
Dictionary<int, string> dictionary = list.ToDictionary();

なんだか、色々な事に使えそうな気がして来ないだろうか?
つづく

LINQは本当に強力だ (1) データ加工の究極の道具

長い間、.NET2.0から知識をアップグレードしていなかったのだが、先日一気に.NET4.0の知識を詰め込んだ。
LINQに触る必要性から、匿名デリゲート・ラムダ式・匿名クラス・式ツリー・拡張メソッドなどを覚えたのだが、はっきり言って今まで勉強を放置してきた事に、激しく後悔している。
「食わず嫌い」だったのは、矢継ぎ早に追加される新しい構文に対する抵抗だったような気がする。C++のテンプレート拡張がもめたらしいのも、気持ちはよくわかる。

テンプレートもそうだが、LINQも言語思想の革命と言っても言い過ぎではないぐらいのインパクトがあった。

LINQの何が良いかと言えば、比類なき拡張性だろう。これを支えているのはIEnumerable<T>インターフェイスと、拡張メソッド構文なわけだが、LINQに触ったことが無い人向けに、興味が持てそうな事を書いてみる(但し無保証 :-) まぁ、もう既に「枯れた」技術になり始めてる気がするので、今更だが)

IEnumerable<T>インターフェイスは、List<T>ジェネリッククラスや配列が実装している。というよりも、「列挙」可能なクラスには、このインターフェイスが実装されている(ちなみに、非ジェネリックなIEnumerableは列挙可能だが、LINQで使うには難点があるので省略)。

このインターフェイスが実装されていれば、以下のようにforeach出来る。

List<string> nameList = new List<string>();
// ...
foreach (string name in nameList)
{
    // 様で呼んでくれなきゃやだ
    if (name.EndsWith("様") == true)
    {
        Console.WriteLine(name);
    }
}

# varはわざと使ってないよ?(一応、.NET 2.0技術者向け)
で、LINQでforeachの部分を以下のように書ける。

// LINQクエリ(まだ実行されない)
IEnumerable<string> kingNames =
    // nameListの個々の要素をnameとして取り出し、
    from name in nameList
    // nameの終端が"様"の場合に、
    where name.EndsWith("様") == true
    // このクエリの結果要素としてnameを返す。
    select name;

// クエリの結果を列挙
foreach (string name in kingNames)
{
    Console.WriteLine(name);
}

「仮」に、nameListの要素数が非常に多かったとする(100万件とか)。
その場合に、以下のように「AsParallel()メソッドを呼び出す」だけで、”様”付きの判定をマルチスレッド化出来る。

IEnumerable kingNames =
    from name in nameList.AsParallel()
    where name.EndsWith("様") == true
    select name;

大量の要素の、しかしながら個々の要素の判定にはそれほどの判定コストを必要としない場合でも、この「PLINQ(パラレルLINQ)」は効率的に動作するはずだ。

通常、マルチスレッドで同じことを行う場合、以下のようなコードとなる。

private readonly List<T> kingNames = new List<T>();
private int count = 1;
private readonly ManualResetEvent final = new ManualResetEvent(false);

// ワークアイテム実行のエントリポイント
private void WorkItemEntry(object argument)
{
    string name = (string)argument;
    if (name.EndsWith("様") == true)
    {
        // 追加が競合しないように、コレクションをロックする
        lock (kingNames)
        {
            kingNames.Add(name);
        }
    }

    // 自分が最後なら通知
    if (Interlocked.Decrement(ref count) == 0)
    {
        final.Set();
    }
}

public IEnumerable<T> GetKingNames()
{
    List<string> nameList = new List<string>();

    // ...

    // ワークアイテムをキューに入れる
    foreach (string name in nameList)
    {
        Interlocked.Increment(ref count);
        ThreadPool.QueueUserWorkItem(WorkItemEntry, name);
    }

    // (キューに入れている間に終わってしまわないように、カウンタの初期値を1にしてあるので減算)
    if (Interlocked.Decrement(ref count) == 0)
    {
        final.Set();
    }

    // 全てのスレッドが終わるのを待つ
    final.WaitOne();
    return kingNames;
}

まず、第一にわかることは、「マルチスレッド対応」にするだけで面倒で不安な同期処理を大量に記述しなければならない事だ。この処理の肝は「EndsWith(“様”)」だけであるのに、付随コードの何とも多い事か。そして、これだけでもLINQで書く事がいかに強力であるかが分かる。

LINQで書く場合、「AsParallel()」で列挙子を修飾するだけだ。AsParallelメソッドは、指定された列挙子(nameList)をParallelQuery<T>クラスでラップする。LINQの他のメソッド同様、これだけではまだクエリは実行されていない。最後にkingNamesをforeachで列挙するまで、全く処理は行われていない。つまり、中間バッファは一切不要という点も重要だ。

元のデータが100万件、EndsWithで絞り込んでも50万件のデータがあるとすれば、判定しては新たなコレクション(中間バッファ)にデータを格納するのはためらわれる。また、列挙した時点で一気に処理を実行することで、CPUのキャッシュにコードが維持される可能性も高まる。おまけに.NETはJITコンパイラで動いているので尚更だ。

次に、レガシーコードで示した方法には、マルチスレッドを効率的に実行できない罠がある。それは、kingNamesに対してロックを行っている部分だ。この実装は、一回のワークアイテム実行に占めるロック時間が、相対的に長すぎる。そのため、複数のスレッドで同時実行されると、ロック競合が多量に発生する。結局その間は「シングルスレッド」と変わらないのだ。おまけにスレッドコンテキストの遷移に時間がかかってしまうので、シングルスレッド性能より落ちてしまう。

「既存コードのマルチスレッド化でパフォーマンス向上」なんて、生易しい事ではないのだ。

それがもっとよく分かるように、このレガシーコードを改良してみる。

// スレッドのエントリポイント
private void ThreadEntry(object argument)
{
    KeyValuePair<Queue<string>, List<string>> pair = (KeyValuePair<Queue<string>, List<string>>)argument;
    Queue<string> queue = pair.Key;
    List<string> localKingNames = pair.Value;

    while (true)
    {
        // キューから文字列を取り出す。最後なら抜けてスレッド終了
        string name = queue.Dequeue();
        if (name == null)
        {
            return;
        }
        if (name.EndsWith("様") == true)
        {
            // コレクションはスレッド固有なのでロック不要
            localKingNames.Add(name);
        }
    }
}

public IEnumerable<T> GetKingNames()
{
    List<string> nameList = new List<string>();

    // ...

    // スレッドに供給する文字列と、結果文字列を格納するコレクションをスレッド数分用意する
    List<KeyValuePair<Queue<string>, List<string>>> pairs = new List<KeyValuePair<Queue<string>, List<string>>>();
    for (int i = 0; i < Environment.ProcessorCount; i++)
    {
        pairs.Add(KeyValuePair<Queue<string>, List<string>>(
            new Queue<string>(), new List<string>());
    }

    // 事前にキューに入れる(スレッド毎に均等に)
    int index = 0;
    foreach (string name in nameList)
    {
        pairs[index].Key.Enqueue(name);
        index = (index + 1) % Environment.ProcessorCount;
    }

    // スレッドを生成して実行を開始する
    List<Thread> threads = new List<Thread>();
    for (int i = 0; i &lt; Environment.ProcessorCount; i++)
    {
        Thread thread = new Thread(ThreadEntry);
        threads.Add(thread);
        thread.Start(pairs[i]);
    }

    // スレッド群が終わるのを待つ
    List<string> kingNames = new List<string>();
    for (int i = 0; i < Environment.ProcessorCount; i++)
    {
        threads[i].Join();
        // 終わったスレッドから、結果を収集する
        kingNames.AddRange(pairs[i].Value);
    }
    return kingNames;
}

あーもう、書いている矢先から面倒で、記事自体無かったことにしようかと5回ぐらい思った :-) (そんな訳で、コンパイルして検証はしていない)

要するにこのコードは、ロックを行わなくて済むように、事前にスレッド毎にデータを分散し、スレッド毎に結果を格納し、その結果はメインスレッド側で収集する、という事をしている。これはマルチスレッドでパフォーマンスを向上させる定石のようなものだ(ロックを不要にする)。キューにデータを入れ直している時点でメモリを余分に使っているため、さらなる改良が必要だが、「もういいだろう」。それにこれ以上書きたくない。

つまり、こういう面倒なことを、ParallelQuery<T>クラスの内部でやってくれるという事だ。そして、現在のCPU事情と言えば、シングルスレッド性能は頭打ちで、マルチコア・SMTを推進している。コードを高速化させるためには、マルチスレッドに対応させるコーディングを行う必要があるが、同期処理は面倒かつバグを生みやすい。上記のような短いコードでさえ、書いただけでは正しいかどうか分からない。

この最適化手法を知っている人なら、これがAsParallelするだけで実現される、なおかつそれはコンパイラが何か怪しげなことをやっているのではなく、全てライブラリだけで実現されていると聞けば、カルチャーショックを受けるはずだ(受けなきゃおかしい)。

#LINQのクエリ構文はコンパイラが解釈する。しかしParallelQuery<T>クラスは種も仕掛けもない、普通のライブラリだ。
#その気になれば、だれでも同じものが作れる。もちろん、安全に実行出来るようにするには高度な技術が必要なので、「俺ParallelQuery<T>」は作らない方がいい。

さて、どのようなコードでも、LINQで書かなければならない訳ではない。例えば、LINQで列挙しながら、列挙中のコレクションを更新するという操作はNGだ。しかし、それはLINQを使わない場合でも同じ事だ(LINQで記述すると、あまりに簡便になるため、出来ない事があると欠点に思えてくるが、それは多分違うよ?)。

また、LINQは「常に構造が変化する要素」を扱うのが難しい。これは当たり前だ。列挙する要素がintになったりstringになったりする状況では、クエリが簡便に記述出来ない(そういう状況が、IEnumerable非ジェネリックインターフェイスを使った場合だ。もっとも、LINQのライブラリはIEnumerableに対応していない。擬似的にIEnumerable<object>で試せば分かる)。

これを以って、やはりLINQは中途半端と思うなら、実用的なコードをLINQで書いてみるべきだ。また、LINQを使うのに、SQL Serverにアクセスする必要などない。IEnumerable<T>インターフェイスを実装していれば、あらゆる要素に応用が可能だ。

「LINQが適している分野ではLINQで書け」

これに尽きる。そしてLINQの「後」でPLINQが生み出されたように、LINQの拡張性にも目を見張るものがある。次回に続く。