LINQでSQLの検索CASE式
LINQのおかげでSQLがすっかり関数型言語の高階関数群にしか見えなくなってしまいました。でもJoinのようなRDBらしい機能はやっぱりよくわからないので、SQLの入門書を借りてきて読んでます。
その中でSQLにもCASE式があることを知ったので、拡張メソッドでCASE式を作り、FizzBuzzを書き直してみました。id:siokoshou:20070712のNyaRuRuさんのコメントのラムダ式でif elseを並べるのに比べて、遅くなるだけで利点は何もないような気がしますが、まぁ書いてみました。クロージャで制御文を実現してみる例と考えるとちょっとはおもしろみがあるのかも?ちなみに、Smalltalkの制御文はクロージャを引数にとる形で実現してるようです。
SQLの検索CASE式の見た目はこう。
CASE WHEN condition1 THEN result1 WHEN condition2 THEN result2 ... ELSE resultN END
完全に真似するのはいろいろ面倒なので、もどきで済ませます(^^;
Whenの部分を、条件式と結果の式を持つクラスにします。Caseに渡す最後のWhenはelseに対応するために、条件式は必ずtrueを返す式とする制限付きです。手抜きです。
Orcasβ1で試しました。最新のCTPはまだ落としてないんで。
using System; using System.Collections.Generic; using System.Linq; class Program { static void Main() { Enumerable.Range( 1, 100 ).Select( m => m.Case( new When<int, string>( n => 0 == n % 15, n => "FizzBuzz" ), new When<int, string>( n => 0 == n % 5, n => "Buzz" ), new When<int, string>( n => 0 == n % 3, n => "Fizz" ), new When<int, string>( n => true, n => n.ToString() ) // else ) ).ToList().ForEach( Console.WriteLine ); Console.ReadKey(); } } public static class Ext { public static TResult Case<TSource, TResult>( this TSource target, params When<TSource, TResult>[] when ) { for ( int i = 0; i < when.Length; i++ ) if ( when[ i ].Condition( target ) ) return when[ i ].Result( target ); return default( TResult ); } } public sealed class When<TSource, TResult> { public Func<TSource, bool> Condition { get; private set; } public Func<TSource, TResult> Result { get; private set; } public When( Func<TSource, bool> cond, Func<TSource, TResult> r ) { this.Condition = cond; this.Result = r; } }
SQLのCASE式というよりは、Schemeのcondですね。
Shiraleeさんに教えてもらったprivateを使ってみました(ありがとうございます)。
「new When
結論。LINQ to ObjectではCaseは無駄に遅いだけで、いらないですね。LINQで遊ぶときは、ついC#の構文を使ったら負けと思ってしまうんだけどw、SQLとC#を混ぜて使える点は大きなメリットなのでどんどん混ぜるべきですね。
ところでLINQ to SQLにCaseはあるんでしょうか?
(追記)このCaseってすべての型を拡張してしまいますね。最悪な例だな…。