ラベル 式木 の投稿を表示しています。 すべての投稿を表示
ラベル 式木 の投稿を表示しています。 すべての投稿を表示

2012年12月15日土曜日

[C#]式木とLINQ

式木がどんな風に役に立っているか、みんな大好き LINQ で見ていきたいと思います。
普段 LINQ はモリモリ使っていますが、その中身はボンヤリとしか理解していなかったのでおさらいしてみました。

LINQと式木の概要

例として LINQ to SQL を挙げてみていきます。LINQ to SQL で以下のようなクエリ式を書いた場合、

IQueryable<Student> query =
    from
        student in db.Student
    where
        student.Gender == "Male" 
    select
        student
    ;

db.Student を foreach して Genderプロパティ が "Male" の項目を探す... ではなく。
ご存知の通り実際にはこんな感じのSQL文が生成され、ADO.NET によって実行されます。

SELECT 
    [t0].[Id], 
    [t0].[Name], 
    [t0].[Gender]
FROM 
    [dbo].[Students] AS [t0]
WHERE 
    [t0].[Gender] = @p0
-- @p0 = 'Male'

このSQL文を作る過程に式木が利用されています。まず、上記のクエリ式は実際にはシンタックスシュガーで、以下の様なメソッド式に変換されます。

db.Student.Where(student => student.Gender == "Male");

さらにWhereメソッドは拡張メソッドなので、こうなりますね。

Where(db.Student, student => student.Gender == "Male");

この式がこんな感じの式木として扱われます。

同等の Expression を組み立てるとこんな感じです。

var whereMethod = typeof (Queryable)
    .GetMethods()
    .First(m => m.Name == "Where")
    .MakeGenericMethod(new Type[] { typeof(Student) });

var paramStudent = Expression.Parameter(typeof (Student), "student");

var expression =
    Expression.Call(whereMethod,
        Expression.Constant(db.Student),
        Expression.Lambda<Func<Student, bool>>(
            Expression.Equal(
                Expression.Property(paramStudent, "Gender"),
                Expression.Constant("Male")
                ),
            paramStudent
            )
        )
        ;

LINQ to SQL では結果を取得する際、まず式木を評価・解析することで適切なSQL文を作成してくれています。

LINQ to XXXXX

式自体(式木)を評価することで、LINQ to SQL や LINQ to Entities では式を SQL に変換しますが、LINQ to XML, LINQ to DataSet, LINQ to NHibernate ... などでは式を各種データソースに適切なアクセス方法に変換しています。

このような仕組みのおかげで統一的な式で、各種データソースへのアクセス、操作をうまく統合しています。
これが LINQ (統合言語クエリ) と呼ばれるゆえんであり、式木を最も活用している例でもあります。

応用してみる(?)

例えば LINQ to SQL では一括の UPDATE や DELETE ができません。例えば先程の例で 性別(Gender) が 男(Male) の生徒を全て削除しようと思うと以下のようになります。

var query =
    from
        student in db.Student
    where
        student.Gender == "Male"
    select
        student
    ;

db.Student.DeleteAllOnSubmit(query);
db.SubmitChanges();

この操作は以下の様な SQL を発行します。まず一度 Gender = 'Male' な生徒を全て取得してから...

SELECT 
    [t0].[Id], 
    [t0].[Name], 
    [t0].[Gender]
FROM 
    [dbo].[Students] AS [t0]
WHERE 
    [t0].[Gender] = @p0
-- @p0 = 'Male'

それぞれ1つずつ DELETE句 を発行していきます。

DELETE FROM [dbo].[Students] WHERE [Id] = @p0 -- @p0 = 1
DELETE FROM [dbo].[Students] WHERE [Id] = @p0 -- @p0 = 2
DELETE FROM [dbo].[Students] WHERE [Id] = @p0 -- @p0 = 3
...

これはあまりスマートとはいえないやり方ですね。本来以下のような SQL を発行すべきです。

DELETE FROM [dbo].[Students] WHERE [Gender] = @p0 -- @p0 = 'Male'

このように LINQ to SQL は更新や削除の操作がちょっと弱いです。しかしこの問題に対しては、自前の拡張メソッドを作って上記のような適切な SQL を発行するように実装することで対処できます。
以下の記事が大変参考になりますので、是非ご覧になってみて下さい。

もうちょっと具体的には

今回ご紹介した LINQ の仕組みは、IQueryableインターフェイス 及び IQueryProviderインターフェイス の組み合わせで実現されています。
以下の記事が大変わかり易く参考になりますので、紹介させて頂きます。

IQueryable と IQueryProvider の仕組みが把握できると、独自の LINQ to XXXXX を作ることができて夢が広がりますね。

さいごに

LINQ初心者の方や、式木がよくわからないぜ... という方の参考になれば幸いです。

※ 余談ですが http://ufcpp.net/ さんの C# の情報の充実度はハンパじゃないですね... C# について何か調べると必ずといっていいほど検索で挙がってきます。MSDNよりわかりやすい事が多く、いつも参考にさせて頂いています。

2012年11月28日水曜日

[C#]式木入門

C#の式木について勉強しながら、勉強したことの理解を書いていきます。

式木ってなに?

式木(Expression tree)とは、式(数式)を木構造で表したものの事です。
以下の様な式を例にします。

int result = 5 + 7 * 3; // result == 26
 

この式をみるとき、加算演算子(+)をAdd関数、乗算演算子(*)をMultiply関数とするとこんな感じですね。

int result = Add(5, Multiply(7, 3));
 

ちょっと改行とかインデントを入れてみます。

int result = 
    Add(
        5, 
        Multiply(
            7, 
            3
            )
        );
 

なんとなく木構造にみえてきませんか?図にしてみます。

はい、どーみても木です。このように式は木で表現することができます。これが式木です。

なににつかうの?

式が木で表せることがわかりました。しかしこれは何の役に立つのでしょうか。

式を組み立てることができる

木構造を作ってあげる事で、好きな式を組み立てることができるようになります。

式を分析することができる

式を木構造にしてやることで、式を分析することができます。

つまり...どういうことだってばよ?

プログラム(式)を作ることができます。またプログラム(式)を分析することができます。

C#での式木

C#(.NET Framework)では式木を扱うための仕組みが用意されています。
System.Linq.Expressions 名前空間に式木を扱うためのオブジェクトが揃っています。

C#の中で式木をオブジェクトとして扱えるということは、C#の中でC#を書くというようなことができるということです。
つまり、プログラムの中でプログラムを作ったり、分析したりできます。いわいるメタプログラミングが可能になります。

さっきの式を組み立ててみる

さっきの簡単な式をC#で組み立てる例を示します。

// 式: 5 + 7 * 3
Expression body = 
    Expression.Add(
        Expression.Constant(5), 
        Expression.Multiply(
            Expression.Constant(7), 
            Expression.Constant(3)
            )
        );
 

このように、式は Expression型 として扱います。Expression型にはいろいろな式を表すための派生型が用意されています。
Expression型にファクトリメソッド(上の例のAddメソッドやConstantメソッドなど)が用意されているので、そこから各派生型のExpressionを作成できます。

加算演算子はAddメソッド、乗算演算子はMultiplyメソッドで作れます。5, 7, 3 などの定数はConstantメソッドで作れます。

このようにして作った式を実行したい場合は、以下のようにします。

Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(body) // () => 5 + 7 * 3;
Func<int> func = lambda.Compile();
int result = func(); // result == 26
 

まず、Expression.Lambdaメソッドを使って、作った式をラムダ式に変換します。この場合は「() => 5 + 7 * 3」というラムダ式ですね。

次に作ったラムダ式を実行可能なデリゲートへコンパイルします。
コンパイルした後は通常のデリゲートと同じなので、普通に実行できます。

まとめ

式木を使うことでC#でメタプログラミングができます。プログラムのなかでプログラムを作るという面白いことが可能です。

こんどは

次は実際に式木がどんな事に役に立つのか、みてみたいと思います。

参考にさせていただきました

Factorio: Space Exploration クリア記録

 工場建設クラフトゲーム、Factorio の MOD である Space Exploration のクリア記録です。 はじめに プレイ時間は約 350 時間、2023年10月から2025年2月にかけて15ヶ月間に及びました。この期間中3人の友人と毎週末、工場勤務に明け暮れました...