c# 式木 (expression tree) ~ linqをより深く理解するために ~

70
C# 式式 Expression Tree LINQ 式式式式式式式式式式式式 式式 式式式 Hokuriku.NET C# 式式 https:// atnd.org/events/57085 2014-10-26

Upload: fujio-kojima

Post on 28-May-2015

2.638 views

Category:

Technology


2 download

DESCRIPTION

Hokuriku.NET C# 勉強会「C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~」 https://atnd.org/events/57085 で使用した資料に加筆 1. LINQ to Objects 復習 2. IQueryable 3. 式木 (Expression Tree) 4. 式木メタ プログラミング 5. LINQ プロバイダー

TRANSCRIPT

Page 1: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

C# 式木Expression Tree

~ LINQ をより深く理解するために ~

小島 富治雄Hokuriku.NET C# 式木

https://atnd.org/events/57085

2014-10-26

Page 2: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

自己紹介

• 小島 富治雄

• @Fujiwo

• http://blog.shos.info

• 福井コンピュータアーキテクト株式会社

• Microsoft MVP C# (2005-2015)

Page 3: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

本日の資料

• スライドhttp://slidesha.re/1tA0Tit

• ソースコードhttp://1drv.ms/1zs3n78

※ スライド中の でソースコードを参照 / 実行のこと

3

ソースコード参照

Page 4: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

本日のゴール

• .NET の式木を理解することで、 LINQ についてより深く理解する

Page 5: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

アジェンダ

1. LINQ to Objects 復習

2. IQueryable<T>

3. 式木 (Expression Tree)

4. 式木メタ プログラミング

5. LINQ プロバイダー

Page 6: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

1. LINQ to Objects 復習

Page 7: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ to Objects

• デモ : LinqToObjectsSample

IEnumerable<int> sequence1 = new[] { 1, 1, 2, 3, 5, 8, 13, 21, 34 };

IEnumerable<int> sequence2 = sequence1.Where (x => x % 2 == 0);

IEnumerable<int> sequence3 = sequence2.Select (x => x * x );

foreach (int item in sequence3)

Console.WriteLine(item);

ソースコード参照

Page 8: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ to Objects

• 「 LINQ は IEnumerable<T> なものへのクエリ」

Page 9: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ to Objects

• デモ : LinqToObjectsSample

IEnumerable<int> sequence1 = new[] { 1, 1, 2, 3, 5, 8, 13, 21, 34 };

IEnumerable<int> sequence2 = sequence1.Where (x => x % 2 == 0);

IEnumerable<int> sequence3 = sequence2.Select (x => x * x );

foreach (int item in sequence3)

Console.WriteLine(item);

実際に sequence3 から値が取り出されるまで、 sequence1 から値は

取り出されず、 Where や Select に渡したデリゲートも実行されない

ソースコード参照

Page 10: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

2. IQueryable<T>

Page 11: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

匿名メソッドとラムダ式の違い

• デモ : 匿名メソッドとラムダ式の違いvar data = new EmployeeDataClassesDataContext();

data.Log = Console.Out;

var sequence1 = data.Employee;

var sequence2 = sequence1.Where (

employee => employee.Name.Contains("田 ") );

var sequence3 = sequence2.Select (

employee => new { 番号 = employee.Id, 名前 = employee.Name });

foreach (var employee in sequence3)

Console.WriteLine("{0}: {1}", employee.番号 , employee.名前 );

ソースコード参照

Page 12: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

匿名メソッドとラムダ式の違い

• 匿名メソッドを渡した場合の SQL:

SELECT [t0].[Id] AS [ 番号 ], [t0].[Name] AS [ 名前 ]

FROM [dbo].[Employee] AS [t0]

WHERE [t0].[Name] LIKE @p0

-- @p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [% 田 %]

SELECT [t0].[Id], [t0].[Name]

FROM [dbo].[Employee] AS [t0]

• ラムダ式を渡した場合の SQL:

Page 13: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

匿名メソッドとラムダ式の違い

• 匿名メソッドを渡した場合の Where:

public static class Queryable

{

public static IQueryable<T> Where<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate);

}

public static class Enumerable

{

public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, int, bool> predicate);

}

• ラムダ式を渡した場合の Where:

Page 14: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

匿名メソッドとラムダ式の違い

• 二つの LINQ

• IEnumerable<T>.Where( 匿名メソッド )

• IQueryable<T>.Where( ラムダ式 )

Page 15: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

匿名メソッドとラムダ式の違い

• デモ : 式として扱えるラムダ式と扱えないラムダ式

class Program

{

static void Main()

{

Func<int, int, int> sequence1 = (x, y) => x + y;

Func<int, int, int> sequence2 = (x, y) => { return x + y; };

Expression<Func<int, int, int>> expression1 = (x, y) => x + y;

//Expression<Func<int, int, int>> expression2 = (x, y) => { return x + y; };

}

}

ブロックが含まれるラムダ式は式として扱えない (IQueryable<T> には使えない )

ソースコード参照

Page 16: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ 構文

• LINQ 構文は、「全てブロックの無いラムダ式」の場合の糖衣構文• なので、 IQueryable<T> の方になる

var sequence4 = from employee in data.Employee

where employee.Name.Contains(" 田 ")

select new { 番号 = employee.Id, 名前 = employee.Name };

Page 17: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

二つの LINQ

• 「 LINQ は IEnumerable<T> なものへのクエリ」

• 「 LINQ は IQueryable<T> なものへのクエリ」

Page 18: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

IQueryable インターフェイス

• IQueryable インターフェイス - MSDN• http://msdn.microsoft.com/ja-jp/library/system.linq.iqueryable(v=vs.110).aspx

public interface IQueryable : IEnumerable

{

Type ElementType { get; }

Expression Expression { get; }

IQueryProvider Provider { get; }

}

public interface IQueryable<T> : IEnumerable<T>, IQueryable

{}

Page 19: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

IQueryable<T>

• デモ : IQueryableSampleclass Foo : IQueryable

{

public Type ElementType

{ get { throw new NotImplementedException(); } }

public Expression Expression

{ get { throw new NotImplementedException(); } }

public IQueryProvider Provider

{ get { throw new NotImplementedException(); } }

public IEnumerator GetEnumerator()

{ throw new NotImplementedException(); }

}

ソースコード参照

Page 20: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

3. 式木 (Expression Tree)

Page 21: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

式木の構造

• デモ : 式の構造を調べてみる

Expression<Func<int, int, int>> expression = (x, y) => x + y;

((Expression)expression).Show();

ソースコード参照

Page 22: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

式木の構造

• (x, y) => x + y

Page 23: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

式木の構造

• Body: x + y

Page 24: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

式木の構造

• Body.Left: x

Page 25: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

式木の構造

• Parameters: (x, y)

Page 26: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

式木の構造

• Parameters[0]: x

Page 27: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

4. 式木メタ プログラミング

Page 28: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

前回は「リフレクション」でしたが…

• リフレクション• 実行時に柔軟に対応

• 型が不明なものとバインドする UI 部品など

• インタフェイスに頼らない規約によるプログラミング

• 実行時に型のメタデータを取得

Assembly Module

Type・ Class・ Interface・ Value Type

FieldInfo

PropertyInfo

EventInfo

MethodInfoConstructorInfo ParameterInfo

Page 29: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

リフレクションの欠点

遅い

Page 30: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

メタプログラミングとは

• 高次なプログラミング• プログラムを対象とするプログラムを書

• プログラムを操作 / 出力するプログラムを書く• プログラムでプログラムを出力すると

手でプログラムを書くよりも効率的な場合が

Page 31: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

実行時の環境に対応

• ビルド時でなく、実行時にプログラムを書きたい

• 「クラスやメソッドはビルド時までに完成してないといけない。実行時には追加 / 変更できない」• なんで ?

Page 32: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

メタプログラミングが有効な例

• コンパイラー / インタープリター• ホスト言語のソースコードから対象言語のプログラムを生成

• O/R マッパー• クラスやオブジェクトから SQL を生成

• モック (mock) オブジェクト• 「モック ( ユニットテストで用いられる代用の

オブジェクト ) を生成」するプログラムを生成

Page 33: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

メタプログラミングが有効な例

• XML や JSON の入出力• 「クラスやオブジェクトから XML や JSON を生

成 /XML や JSON からクラスやオブジェクトを生成」するプログラムを生成

• Web アプリケーション• クライアント側で動作するプログラム

(JavaScript 等 ) をサーバー側で動的に生成

Page 34: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

参考 : .NET でのメタプログラミングの例

Page 35: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

参考 :CodeDOM (Code Document Object Model)

• System.CodeDom 名前空間や System.CodeDom.Compiler 名前空間• C# や Visual Basic 等のコードを生成

• コンパイルしてアセンブリを生成

CodeDOM CodeDOMProvider

ソース コード(C# 、 VB 、 JScript

)

アセンブリ

GenerateCodeFromNamespace

CompileAssemblyFromDom

Page 36: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

参考 : リフレクション

• System.Reflection 名前空間• クラスやインスタンスに関する情報 ( メ

タデータ ) を取得し、メンバーを呼び出したりできる

• System.Reflection.Emit 名前空間• CIL (Common Intermediate

Language) からクラス等を動的生成

Page 37: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

参考 : CodeDOM によるメタプログラミング

• デモ : CodeDomHelloWorldnamespace CodeDomHelloWorldDemo{ using System;

class Program { static void Main() { Console.WriteLine("Hello world!"); Console.ReadKey(); } }}

ソースコード参照

Page 38: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

参考 : Ildasm.exe (IL 逆アセンブラー )

• アセンブリを逆アセンブルして中間言語 (IL: Intermediate Language) を表示

• Visual Studio の「開発者コマンド プロンプト」から Ildasm.exe を起動

• Ildasm.exe (IL 逆アセンブラー ) – MSDN• http://msdn.microsoft.com/ja-jp/library/f7dy01k1(v=vs.110).aspx

Page 39: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

式木 によるメタプログラミング

• System.Linq.Expressions 名前空間• 式木を生成し、動的にプログラムを生成

Page 40: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

式木

Expression<Func<Employee, bool>> expression = employee => employee.Name.Contains(" 山 ");

Parameters

Body Object

Method

Arguments

Expression

Member

employee => employee.Name.C

ontains(" 山 ")employee.Name

Contains

employee

Name

“ 山”

employee

employee.Name.Contains(" 山 ")

Page 41: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

Visual Studio のデバッガーで add 式の構造を見る

Page 42: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

例 : 式木による Add メソッドの動的生成

• 簡単なメソッド (Add) の生成1.式木を組み立てる

2.コンパイル

+

x y

=>

(x, y)

Page 44: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

(x, y) => x + y の式木

パラメーターの x と y は、 (x, y) 部分と x + y 部分で使われているが、それぞれ 1 インスタンスずつにする

Page 45: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

式木によるメタプログラミング

• デモ : ラムダ式を組み立ててデリゲートとして実行する

static Func<int, int, int> AddByExpression(){ // 生成したい式 // (int x, int y) => x + y

// 引数 x の式 var x = Expression.Parameter(type: typeof(int)); // 引数 y の式 var y = Expression.Parameter(type: typeof(int)); // x + y の式 var add = Expression.Add (left: x, right: y); // (x, y) => x + y の式 var lambda = Expression.Lambda (add, x, y ); // ラムダ式をコンパイルしてデリゲートとして返す return (Func<int, int, int>)lambda.Compile();}

ソースコード参照

Page 46: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

式木によるメタプログラミング

• デモ : AddSamplestatic Func<int, int, int> AddByExpression(){ // 生成したい式 // (int x, int y) => x + y

// 引数 x の式 var x = Expression.Parameter(type: typeof(int)); // 引数 y の式 var y = Expression.Parameter(type: typeof(int)); // x + y の式 var add = Expression.Add (left: x, right: y); // (x, y) => x + y の式 var lambda = Expression.Lambda (add, x, y ); // ラムダ式をコンパイルしてデリゲートとして返す return (Func<int, int, int>)lambda.Compile();}

ソースコード参照

Page 47: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

メタプログラミングの実行速度 : Demo

• 生成速度の比較1. Reflection.Emit による Add

2. 式木による Add

3. Roslyn による Add

• 実行速度の比較1. 静的な Add

2. Reflection.Emit による Add

3. 式木による Add

4. Roslyn による Add

Page 48: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

メタプログラミングの実行速度 : 結果

• 生成速度の比較

Page 49: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

メタプログラミングの実行速度 : 結果

• 実行速度の比較

Page 50: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

式木によるメタプログラミング• デモ : CallSample// Expression ( 式 ) によるメソッド呼び出しメソッドの生成static Func<T, TResult> CallByExpression<T, TResult>(string methodName){ // 生成したい式の例 : // (T item) => item.methodName()

// 引数 item の式 var parameterExpression = Expression.Parameter(type: typeof(T), name: "item"); // item.methodName() の式 var callExpression = Expression.Call( instance: parameterExpression, method : typeof(T).GetMethod(methodName, Type.EmptyTypes) ); // item => item.methodName() の式 var lambda = Expression.Lambda(callExpression, parameterExpression); // ラムダ式をコンパイルしてデリゲートとして返す return (Func<T, TResult>)lambda.Compile();}

ソースコード参照

Page 51: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

メタプログラミングの実行速度の改善

• 生成が遅い• 呼び出しの度に生成すると遅い

→ キャッシュで改善

Page 52: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

生成したデリゲートのキャッシュ

• デリゲートのキャッシュを用意Dictionary<string, Delegate> methods = new Dictionary<string, Delegate>();

1.キャッシュにない場合は、デリゲートを生成してキャッシュに入れる

2.キャッシュ内のデリゲートを呼ぶ

Page 53: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

メタプログラミングの実行速度の改善 : Demo• メソッド呼び出しの速度の比較

1. 静的なメソッド呼び出し

2. 動的なメソッド呼び出し1. リフレクション

2. dynamic

3. 動的にメソッドを生成して呼び出し - キャッシュ無し1. Reflection.Emit

2. 式木

3. Roslyn

4. 動的メソッドを生成して呼び出し - キャッシュ有り1. Reflection.Emit

2. 式木

3. Roslyn

Page 54: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

メタプログラミングの実行速度 : 結果

• 生成速度の比較

Page 55: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

メタプログラミングの実行速度 : 結果

• 実行速度の比較

Page 56: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

メタプログラミングの実行速度の改善 : 結果

Page 57: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

5. LINQ プロバイダー

Page 58: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ プロバイダー

• LINQ to Xxx

• LINQ to SQL 、 LINQ to Entities

• LINQ to Outlook 等

• LINQ プロバイダまとめ• http://blog.jhashimoto.net/entry/20120616/1339806360

Page 59: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ プロバイダーの作り方

• チュートリアル : IQueryable LINQ プロバイダーの作成 - MSDN

• http://msdn.microsoft.com/ja-jp/library/bb546158.aspx

• LINQ: Building an IQueryable provider series

• http://blogs.msdn.com/b/mattwar/archive/2008/11/18/linq-links.aspx

• Writing custom LINQ provider

• http://weblogs.asp.net/mehfuzh/writing-custom-linq-provider

Page 60: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ プロバイダーの作り方

LINQ プロバイダー(IQueryProvider)

クエリー コンテキスト

クエリー(IQueryable)

式を解釈

Page 61: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ プロバイダーの作り方

• IQueryable<T> を実装したクラスを作る

• IQueryProvider を実装したクラスを作る• IQueryable<T> なクラスの Provider に設定

• Execute の中で渡された式木 (Expression) を解析して、 IQueryable<T> を返す

Page 62: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ プロバイダーの作り方

pubic class QueryProvider : IQueryProvider{ public IQueryable<TCollection> CreateQuery<TCollection>(Expression expression) { return new QueryableData<TCollection>(this, expression); }

IQueryable IQueryProvider.CreateQuery(Expression expression) { return null; }

public TResult Execute<TResult>(Expression expression) { return default(TResult); }

public object Execute(Expression expression) { // ここで式木を解釈して、 IEnumerable を作って返す }}

• デモ : ProviderSample ソースコード参照

Page 63: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ プロバイダーの作り方

• IQueryProvider.Execute で式木を解釈

• ExpressionVisitor クラス• Visitor パターンで式木中の式を探索

Page 64: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ プロバイダーを作ろうとしてみよう• デモ : ProviderSamplestatic void Main(){ IQueryable<int> query1 = new QueryableData<int>(new QueryProvider()); Console.WriteLine(query1.Expression);

IQueryable<int> query2 = query1.Where(x => x % 2 == 0); Console.WriteLine(query2.Expression);

IQueryable<int> query3 = query2.OrderBy(x => x); Console.WriteLine(query3.Expression);

IQueryable<int> query4 = query3.Select(x => x * x); Console.WriteLine(query4.Expression);

foreach (int item in query4) Console.WriteLine(item);}

ソースコード参照

Page 65: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ プロバイダーを作ろうとしてみよう• ProviderSample の実行結果value(ProviderSample.QueryableData`1[System.Int32])

value(ProviderSample.QueryableData`1[System.Int32]).Where(x => ((x % 2) == 0))

value(ProviderSample.QueryableData`1[System.Int32]).Where(x => ((x % 2) == 0)).OrderBy(x => x)

value(ProviderSample.QueryableData`1[System.Int32]).Where(x => ((x % 2) == 0)).OrderBy(x => x).Select(x => (x * x))

1

1

2

3

5

8

13

21

34

Page 66: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ プロバイダーの作り方

• 実際に値を取り出すまでは、 IQueryable<T> なオブジェクトの中に式木が組み上がっていくだけ

• 値を取り出すと、 IQueryProvider.Execute が呼ばれるので、その中で式木を解析

Page 67: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

ExpressionVisitor で式を解析

• デモ : ExpressionVisitorSample

public class MyExpressionVisitor : ExpressionVisitor{ protected override Expression VisitBinary(BinaryExpression expression) { return base.VisitBinary(expression); }

protected override Expression VisitConstant(ConstantExpression expression) { return base.VisitConstant(expression); }

protected override Expression VisitMethodCall(MethodCallExpression expression) { return base.VisitMethodCall(expression); }

protected override Expression VisitParameter(ParameterExpression expression) { return base.VisitParameter(expression); }

…… 等々 ……}

ソースコード参照

Page 68: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

ExpressionVisitor で式を解析

• ExpressionVisitorSample の実行結果☆ 式 (x, y) => x + y

二項演算 ((x + y)) - 右辺 : x, 左辺 : y, 型 : System.Int32

引数 (x) - 名前 : x, 型 : System.Int32

引数 (y) - 名前 : y, 型 : System.Int32

引数 (x) - 名前 : x, 型 : System.Int32

引数 (y) - 名前 : y, 型 : System.Int32

☆ 式 text => text.Contains(" 福 ")

メソッドコール (text.Contains(" 福 ")) - メソッド名 : Contains, 型 : System.Boolean

引数 (text) - 名前 : text, 型 : System.String

定数 (" 福 ") - 値 : 福 , 型 : System.String

引数 (text) - 名前 : text, 型 : System.String

Page 69: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

LINQ to Twitter を作ろうとしてみよう

• デモ : LinqToTwitterSamplepublic class QueryableTimeline<TElement> : IOrderedQueryable<TElement>{ public IQueryProvider Provider { get; private set; } public Expression Expression { get; private set; }

public Type ElementType { get { return typeof(TElement); } }

public QueryableTimeline() { Provider = new TimelineQueryProvider(); Expression = Expression.Constant(this); }

…… 途中省略 ……}

ソースコード参照

Page 70: C# 式木 (Expression Tree) ~ LINQをより深く理解するために ~

まとめ

• .NET の式木を理解することで、LINQ についてより深く理解することができた