(Disclaimer:如果需要轉載請先與我聯(lián)系
作者:RednaxelaFX at rednaxelafx.javaeye.com)
系列文章:
LINQ與DLR的Expression tree(1):簡(jiǎn)介L(cháng)INQ與Expression treeLINQ與DLR的Expression tree(2):簡(jiǎn)介DLRLINQ與DLR的Expression tree(3):LINQ與DLR的樹(shù)的對比LINQ與DLR的Expression tree(4):創(chuàng )建靜態(tài)類(lèi)型的LINQ表達式樹(shù)節點(diǎn)LINQ與DLR的Expression tree(5):用lambda表達式表示常見(jiàn)控制結構
LINQ Expression tree能直接表示的語(yǔ)法結構在
上一篇中已經(jīng)詳細的列了出來(lái),包括算術(shù)、按位、邏輯等運算符的表達式、條件表達式、方法調用表達式、對象創(chuàng )建表達式等。要表示一般的命令式程序,需要支持3中基本控制流語(yǔ)法結構:順序、分支、循環(huán)。在C#中這三種結構能很自然的用語(yǔ)句來(lái)表示,但有語(yǔ)句的lambda表達式無(wú)法用LINQ Expressiontree表示。下面就讓我們來(lái)看看這三種結構如何轉換成只用表達式的方式來(lái)表示。
需要事先說(shuō)明三點(diǎn):
1、只要避免使用賦值相關(guān)的表達式,一個(gè)只包含表達式的lambda表達式既可以轉換為委托也可以轉換為Expressiontree;手工創(chuàng )建Expressiontree比較麻煩,本文的例子無(wú)特別說(shuō)明都將通過(guò)轉換為委托的lambda表達式來(lái)表示,轉換為Expressiontree的版本留待后續文章再詳細給出。
2、能夠用Expression tree表示的東西不一定能被所有LINQ provider接受。這點(diǎn)必須注意。也就是說(shuō)即使能把一個(gè)語(yǔ)句塊變成一個(gè)表達式,也未必能用在LINQ to SQL等場(chǎng)景里。
3、下面演示的轉換方式絕對不代表任何best practice;.NET中方法調用的開(kāi)銷(xiāo)是不可忽視的,因而轉換后的表達式肯定會(huì )比原本的語(yǔ)句慢。本文只是著(zhù)重說(shuō)明轉換的可能性。
局部變量的模擬局部變量,顧名思義,在方法外是看不到的;方法調用結束后局部變量也就隨即消失(如果不涉及閉包)。它們的作用主要是保存一些中間的計算結果,將很長(cháng)的表達式分割成一些比較短的表達式,使得程序代碼更為清晰。一般在C#中使用局部變量需要聲明語(yǔ)句和賦值表達式,而這兩種結構LINQExpression tree都無(wú)法支持。怎么辦呢?
許多情況下可以把局部變量去掉,把短表達式拼接回到長(cháng)的表達式,避免使用語(yǔ)句。一個(gè)簡(jiǎn)單的例子:
- ( int x, int y ) => {
- var strX = x.ToString( "X" );
- var strY = y.ToString( "x" );
- Console.WriteLine( "x = {0}, y = {1}", strX, strY );
- }
( int x, int y ) => {var strX = x.ToString( "X" );var strY = y.ToString( "x" );Console.WriteLine( "x = {0}, y = {1}", strX, strY );}可以很順利的變成:
- ( int x, int y ) =>
- Console.WriteLine( "x = {0}, y = {1}", x.ToString( "X" ), y.ToString( "x" ) )
( int x, int y ) =>Console.WriteLine( "x = {0}, y = {1}", x.ToString( "X" ), y.ToString( "x" ) )但這是不是唯一的選擇呢?或許計算某個(gè)中間結果的開(kāi)銷(xiāo)很大,而后面的計算中要多次用到它;或許計算某個(gè)中間結果會(huì )對產(chǎn)生副作用,而我們只希望副作用發(fā)生一次;有許多情況下我們還是需要局部變量的。怎么辦呢?
注意到,LINQ Expression tree雖然不支持聲明局部變量,卻允許聲明和調用嵌套的lambda表達式。將中間的計算結果以參數的形式傳給一個(gè)嵌套的lambda表達式,也就等同于使用了局部變量。例子:
- ( ) => {
- var input = Console.ReadLine( );
- var lowerCase = input.ToLower( );
- var upperCase = input.ToUpper( );
- Console.WriteLine( "lower case: {0}\nupper case: {1}", lowerCase, upperCase );
- }
( ) => {var input = Console.ReadLine( );var lowerCase = input.ToLower( );var upperCase = input.ToUpper( );Console.WriteLine( "lower case: {0}\nupper case: {1}", lowerCase, upperCase );}變成:
- ( ) => ( ( Action<string> ) ( s =>
- Console.WriteLine( "lower case: {0}\nupper case: {1}",
- s.ToLower( ), s.ToUpper( ) )
- ) )( Console.ReadLine( ) )
( ) => ( ( Action<string> ) ( s =>Console.WriteLine( "lower case: {0}\nupper case: {1}",s.ToLower( ), s.ToUpper( ) )) )( Console.ReadLine( ) )注意到Console.ReadLine()是有副作用的,這個(gè)方法調用會(huì )消耗掉輸入流里的一行,顯然我們不希望調用它兩次。
將語(yǔ)句轉換為表達式之后,原本寫(xiě)在前面的語(yǔ)句變成寫(xiě)在后面的表達式了,不習慣的話(huà)看起來(lái)或許很不自然,但注意到C#采用了嚴格求值,要先把參數的值求出來(lái)之后才執行方法調用,所以轉換后的寫(xiě)法的執行順序與轉換前是一致的。
例子中的強制類(lèi)型轉換((Action<string>))是必要的,編譯器需要它才知道該生成委托還是Expressiontree。實(shí)際上只要最外層的lambda表達式是賦值給Expression<TDelegate>類(lèi)型的變量,整個(gè)lambda表達式(包括內層嵌套的)都會(huì )生成為Expression tree,像這樣:
- Expression<Action> expr = ( ) => ( ( Action<string> ) ( s =>
- Console.WriteLine( "lower case: {0}\nupper case: {1}",
- s.ToLower( ), s.ToUpper( ) )
- ) )( Console.ReadLine( ) );
Expression<Action> expr = ( ) => ( ( Action<string> ) ( s =>Console.WriteLine( "lower case: {0}\nupper case: {1}",s.ToLower( ), s.ToUpper( ) )) )( Console.ReadLine( ) );會(huì )由編譯器生成為:
- var s = Expression.Parameter( typeof( string ), "s" );
- Expression<Action> expr = Expression.Lambda<Action>(
- Expression.Invoke(
- Expression.Convert(
- Expression.Lambda<Action<string>>(
- Expression.Call(
- null,
- typeof( Console ).GetMethod(
- "WriteLine",
- new Type[ ] {
- typeof( string ),
- typeof( object ),
- typeof( object )
- }
- ),
- new Expression[ ] {
- Expression.Constant(
- "lower case: {0}\nupper case: {1}",
- typeof( string )
- ),
- Expression.Call(
- s,
- typeof( string ).GetMethod( "ToLower", Type.EmptyTypes ),
- new Expression[ 0 ]
- ),
- Expression.Call(
- s,
- typeof( string ).GetMethod( "ToUpper", Type.EmptyTypes ),
- new Expression[ 0 ]
- )
- }
- ),
- new [ ] { s }
- ),
- typeof( Action<string> )
- ),
- new Expression[] {
- Expression.Call(
- null,
- typeof( Console ).GetMethod( "ReadLine", Type.EmptyTypes ),
- new Expression[ 0 ]
- )
- }
- ),
- new ParameterExpression[ 0 ]
- );
var s = Expression.Parameter( typeof( string ), "s" );Expression<Action> expr = Expression.Lambda<Action>(Expression.Invoke(Expression.Convert(Expression.Lambda<Action<string>>(Expression.Call(null,typeof( Console ).GetMethod("WriteLine",new Type[ ] {typeof( string ),typeof( object ),typeof( object )}),new Expression[ ] {Expression.Constant("lower case: {0}\nupper case: {1}",typeof( string )),Expression.Call(s,typeof( string ).GetMethod( "ToLower", Type.EmptyTypes ),new Expression[ 0 ]),Expression.Call(s,typeof( string ).GetMethod( "ToUpper", Type.EmptyTypes ),new Expression[ 0 ])}),new [ ] { s }),typeof( Action<string> )),new Expression[] {Expression.Call(null,typeof( Console ).GetMethod( "ReadLine", Type.EmptyTypes ),new Expression[ 0 ])}),new ParameterExpression[ 0 ]);注意到里面嵌套的lambda表達式也被編譯為Expression tree而不是一個(gè)委托。如果手工創(chuàng )建這棵Expressiontree則可以省去類(lèi)型轉換的那步,也就是等同于將上述代碼的第4、35、36這三行注釋掉,因為那個(gè)類(lèi)型轉換本身并沒(méi)有意義,只是為了讓編譯器得到足夠的類(lèi)型信息來(lái)轉換lambda表達式而已。
順序結構的模擬語(yǔ)句與表達式最大的區別就是前者一般沒(méi)有值或忽略值,而后者一定有值。因而語(yǔ)句只能通過(guò)分隔符一個(gè)個(gè)按順序寫(xiě),而不能像表達式可以用運算符結合起來(lái)或作為參數傳遞。在用表達式模擬順序結構時(shí),最大的障礙就是返回值類(lèi)型為void的方法——它們不返回任何值,無(wú)法放在表達式里與表達式的其它部分組合起來(lái)。如果能有辦法把void變成null也好……所謂“遇到問(wèn)題的時(shí)候只要增加一個(gè)間接層就能解決”
而.NET就提供了這么一種辦法。所有委托的基類(lèi),Delegate類(lèi)上有一個(gè)DynamicInvoke()方法,參數類(lèi)型是paramsobject[],返回值類(lèi)型是object,正好滿(mǎn)足我們的需要。于是像Console.WriteLine()這種返回void的方法,可以包裝為一個(gè)lambda表達式,然后用DynamicInvoke()包裝起來(lái):
- ( ( Action ) ( ( ) => Console.WriteLine( ) ) ).DynamicInvoke( )
( ( Action ) ( ( ) => Console.WriteLine( ) ) ).DynamicInvoke( )
這樣的調用將總是返回null,因為Action系的委托忽略返回值,而Delegate.DynamicInvoke()對返回值類(lèi)型為void的委托類(lèi)型總是返回null。這樣就有機會(huì )用到C#里方便的??運算符把語(yǔ)句變成表達式串起來(lái)了——??運算符是一個(gè)具有短路求值性質(zhì)的運算符,語(yǔ)義是:當左操作數不為null時(shí)值為左操作數的值,否則為右操作數的值。舉例來(lái)說(shuō),把這兩個(gè)語(yǔ)句:
- Console.WriteLine( "1" );
- Console.WriteLine( "2" );
Console.WriteLine( "1" );Console.WriteLine( "2" );
變成:
- ( ( Action ) ( ( ) => Console.WriteLine( "1" ) ) ).DynamicInvoke( ) ??
- ( ( Action ) ( ( ) => Console.WriteLine( "2" ) ) ).DynamicInvoke( )
( ( Action ) ( ( ) => Console.WriteLine( "1" ) ) ).DynamicInvoke( ) ??( ( Action ) ( ( ) => Console.WriteLine( "2" ) ) ).DynamicInvoke( )
把這種轉換方式應用在lambda表達式里,就可以把這個(gè)帶有語(yǔ)句塊的lambda表達式:
- ( ) => {
- var s = Console.ReadLine( );
- Console.WriteLine( s.ToLower( ) );
- Console.WriteLine( s.ToUpper( ) );
- }
( ) => {var s = Console.ReadLine( );Console.WriteLine( s.ToLower( ) );Console.WriteLine( s.ToUpper( ) );}(注意這里的s是一個(gè)局部變量)
變成一個(gè)只有表達式的lambda表達式:
- ( ) =>
- ( ( Func<string, object> ) ( s => (
- ( ( Action ) ( ( ) => Console.WriteLine( s.ToLower( ) ) ) ).DynamicInvoke( ) ??
- ( ( Action ) ( ( ) => Console.WriteLine( s.ToUpper( ) ) ) ).DynamicInvoke( ) )
- ) )( Console.ReadLine( ) )
( ) =>( ( Func<string, object> ) ( s => (( ( Action ) ( ( ) => Console.WriteLine( s.ToLower( ) ) ) ).DynamicInvoke( ) ??( ( Action ) ( ( ) => Console.WriteLine( s.ToUpper( ) ) ) ).DynamicInvoke( ) )) )( Console.ReadLine( ) )
這個(gè)lambda表達式就能夠被LINQ Expression tree所表示了。
留意一下這個(gè)例子里的s,在最內層的lambda表達式里是不是參數也不是局部變量,而是自由變量,體現了C#的lambda表達式具有閉包的功能。如果沒(méi)有閉包的功能,s就得出現在每個(gè)最內層lambda表達式的參數表里,那就麻煩了。
分支結構的模擬最基本的分支結構是if-else語(yǔ)句,對應的有?:運算符表示的條件表達式。這兩者看似能直接對應,語(yǔ)句與表達式的區別再次體現了出來(lái):語(yǔ)句沒(méi)有或忽略值,而表達式必須有值。因而,if語(yǔ)句可以沒(méi)有else子句,也不關(guān)心返回值的問(wèn)題;但條件表達式必須同時(shí)有true和false的分支,且兩個(gè)分支的值的類(lèi)型必須匹配。舉例來(lái)說(shuō):
- static int Abs( int x ) {
- if ( 0 > x ) return -x;
- else return x;
- }
static int Abs( int x ) {if ( 0 > x ) return -x;else return x;}很容易變?yōu)楸磉_式:
- ( int x ) => ( 0 > x ) ? -x : x
( int x ) => ( 0 > x ) ? -x : x
但這個(gè)函數就無(wú)法直接表示為表達式了:
- static void PrintIfEven( int x ) {
- if ( 0 == x % 2 ) Console.WriteLine( x );
- }
static void PrintIfEven( int x ) {if ( 0 == x % 2 ) Console.WriteLine( x );}要轉換,就需要應用到上一節提到的包裝技巧:
- ( int x ) => ( 0 == x % 2 ) ?
- ( ( Action ) ( ( ) => Console.WriteLine( x ) ) ).DynamicInvoke( ) :
- null
( int x ) => ( 0 == x % 2 ) ?( ( Action ) ( ( ) => Console.WriteLine( x ) ) ).DynamicInvoke( ) :null
再次注意到閉包的應用:最內層的lambda表達式使用了自由變量x。
switch語(yǔ)句可以看作是if-else串起來(lái),轉換為表達式的話(huà),串聯(lián)使用條件表達式就行,有需要的時(shí)候加上包裝。例如:
- static string Grade( int point ) {
- switch ( point / 10 ) {
- case 10:
- case 9:
- return "A";
- case 8:
- return "B";
- case 7:
- return "C";
- case 6:
- return "D";
- default:
- return "F";
- }
- }
static string Grade( int point ) {switch ( point / 10 ) {case 10:case 9:return "A";case 8:return "B";case 7:return "C";case 6:return "D";default:return "F";}}簡(jiǎn)單的變成:
- ( int point ) => ( ( Func<int, string> ) ( i =>
- ( 9 == i || 10 == i ) ? "A" :
- ( 8 == i ) ? "B" :
- ( 7 == i ) ? "C" :
- ( 6 == i ) ? "D" :
- "F"
( int point ) => ( ( Func<int, string> ) ( i =>( 9 == i || 10 == i ) ? "A" :( 8 == i ) ? "B" :( 7 == i ) ? "C" :( 6 == i ) ? "D" :/* else */ "F"
循環(huán)結構的模擬用命令式語(yǔ)言的思維方式,循環(huán)語(yǔ)句無(wú)法轉換為表達式。但我們可以一步步來(lái)尋找突破口。
首先找出循環(huán)本質(zhì)上需要怎樣的支持。有兩點(diǎn):1、需要有終止條件;2、需要能夠在一輪循環(huán)結束后讓控制流跳轉回到循環(huán)體的開(kāi)始處。另外,大多循環(huán)還需要用到循環(huán)變量,要能夠更新循環(huán)變量的值。
假如不能使用局部變量來(lái)做循環(huán)變量,回想到本文開(kāi)頭提到的轉換局部變量的方法,可以把循環(huán)變量放在參數里,把循環(huán)體變成一個(gè)函數。這樣,循環(huán)就變成遞歸的形式了,這也正好解決了控制流跳轉的問(wèn)題。事實(shí)上在函數式語(yǔ)言里,迭代(iteration)就是用這樣的遞歸來(lái)實(shí)現的。
于是,如何把循環(huán)語(yǔ)句轉換為表達式的問(wèn)題就變成了如何用lambda表達式表示遞歸函數的問(wèn)題。問(wèn)題是,lambda表達式?jīng)]有名字——它就是匿名函數。沒(méi)有名字的話(huà),如何遞歸呢?肯定會(huì )有人想到這種做法:
- Func<int, int> factorial = null;
- factorial = x => ( 0 == x ) ? 1 : x * factorial( x - 1 );
Func<int, int> factorial = null;factorial = x => ( 0 == x ) ? 1 : x * factorial( x - 1 );
在C#里這是可行的,但不徹底。這里我們要用的辦法,是通過(guò)找到一個(gè)函數的不動(dòng)點(diǎn)(fix-point)來(lái)實(shí)現lambda表達式的遞歸。具體的原理留待以后的文章再詳細討論,這里先給出結果來(lái)滿(mǎn)足一下眼球。通過(guò)這樣一個(gè)輔助類(lèi):
- delegate T SelfApplicable<T>( SelfApplicable<T> func );
-
- public static class Functional<T, TR> {
- private static readonly
- SelfApplicable<Func<Func<Func<T, TR>, Func<T, TR>>, Func<T, TR>>> _yCombinator =
- y => f => x => f( y( y )( f ) )( x );
- private static readonly
- Func<Func<Func<T, TR>, Func<T, TR>>, Func<T, TR>> _fixPointGenerator =
- _yCombinator( _yCombinator );
-
- public static Func<Func<Func<T, TR>, Func<T, TR>>, Func<T, TR>> Fix {
- get { return _fixPointGenerator; }
- }
- }
delegate T SelfApplicable<T>( SelfApplicable<T> func );public static class Functional<T, TR> {private static readonlySelfApplicable<Func<Func<Func<T, TR>, Func<T, TR>>, Func<T, TR>>> _yCombinator =y => f => x => f( y( y )( f ) )( x );private static readonlyFunc<Func<Func<T, TR>, Func<T, TR>>, Func<T, TR>> _fixPointGenerator =_yCombinator( _yCombinator );public static Func<Func<Func<T, TR>, Func<T, TR>>, Func<T, TR>> Fix {get { return _fixPointGenerator; }}}我們可以做到這種效果:
- Func<int, int> factorial = Functional<int, int>.Fix(
- fac => x => ( 0 == x ) ? 1 : x * fac( x - 1) );
- factorial( 5 );
Func<int, int> factorial = Functional<int, int>.Fix(fac => x => ( 0 == x ) ? 1 : x * fac( x - 1) );factorial( 5 ); // 120
注意到這種做法只用了lambda表達式,所以也可以轉換為使用Expression tree來(lái)完成整個(gè)過(guò)程。把上述輔助類(lèi)里有效的內容提取出來(lái),我們來(lái)看看只用Expression tree實(shí)現的版本:
- using System;
- using System.Linq.Expressions;
-
- static class TestRecursiveExpressionTree {
- static void Main( string[ ] args ) {
- ExpressionTreeTest( 5 );
- LambdaTest( 4 );
- }
-
- delegate T SelfApplicableExpr<T>( Expression<SelfApplicableExpr<T>> self );
-
- static void ExpressionTreeTest( int num ) {
-
- var y = Expression.Parameter(
- typeof(
- Expression<SelfApplicableExpr<
- Expression<Func<
- Expression<Func<
- Expression<Func<int, int>>,
- Expression<Func<int, int>>
- >>,
- Expression<Func<int, int>>
- >>
- >>
- ),
- "y"
- );
- var f = Expression.Parameter(
- typeof(
- Expression<Func<
- Expression<Func<int, int>>,
- Expression<Func<int, int>>
- >>
- ),
- "f"
- );
- var x = Expression.Parameter( typeof( int ), "x" );
- var fac = Expression.Parameter( typeof( Expression<Func<int, int>> ), "fac" );
-
-
- var Y = Expression.Lambda<SelfApplicableExpr<
- Expression<Func<
- Expression<Func<
- Expression<Func<int, int>>,
- Expression<Func<int, int>>
- >>,
- Expression<Func<int, int>>
- >>
- >>(
- Expression.Quote(
- Expression.Lambda<
- Func<
- Expression<Func<
- Expression<Func<int, int>>,
- Expression<Func<int, int>>
- >>,
- Expression<Func<int, int>>
- >
- >(
- Expression.Quote(
- Expression.Lambda<Func<int, int>>(
- Expression.Invoke(
- Expression.Call(
- Expression.Invoke(
- Expression.Call(
- f,
- typeof(
- Expression<Func<
- Expression<Func<int, int>>,
- Expression<Func<int, int>>
- >>
- ).GetMethod( "Compile" ),
- new Expression[ ] { }
- ),
- new Expression[ ] {
- Expression.Invoke(
- Expression.Call(
- Expression.Invoke(
- Expression.Call(
- y,
- typeof(
- Expression<SelfApplicableExpr<
- Expression<Func<
- Expression<Func<
- Expression<Func<int, int>>,
- Expression<Func<int, int>>
- >>,
- Expression<Func<int, int>>
- >>
- >>
- ).GetMethod( "Compile" ),
- new Expression[ ] { }
- ),
- new Expression[ ] { y }
- ),
- typeof(
- Expression<Func<
- Expression<Func<
- Expression<Func<int, int>>,
- Expression<Func<int, int>>
- >>,
- Expression<Func<int, int>>
- >>
- ).GetMethod( "Compile" ),
- new Expression[ ] { }
- ),
- new Expression[ ] { f }
- )
- }
- ),
- typeof(
- Expression<Func<int, int>>
- ).GetMethod( "Compile" ),
- new Expression[ ] { }
- ),
- new Expression[ ] { x }
- ),
- new ParameterExpression[ ] { x }
- )
- ),
- new ParameterExpression[ ] { f }
- )
- ),
- new ParameterExpression[ ] { y }
- );
-
-
- var Fix = Y.Compile( )( Y );
-
-
- var F = Expression.Lambda<
- Func<
- Expression<Func<int, int>>,
- Expression<Func<int, int>>
- >
- >(
- Expression.Quote(
- Expression.Lambda<Func<int, int>>(
- Expression.Condition(
- Expression.Equal(
- x,
- Expression.Constant(
- 0,
- typeof( int )
- )
- ),
- Expression.Constant(
- 1,
- typeof( int )
- ),
- Expression.Multiply(
- x,
- Expression.Invoke(
- Expression.Call(
- fac,
- typeof( Expression<Func<int, int>> ).GetMethod( "Compile" ),
- new Expression[ ] { }
- ),
- new Expression[ ] {
- Expression.Subtract(
- x,
- Expression.Constant(
- 1,
- typeof( int )
- )
- )
- }
- )
- )
- ),
- new ParameterExpression[ ] { x }
- )
- ),
- new ParameterExpression[ ] { fac }
- );
-
-
- var factorial = Fix.Compile( )( F );
-
-
- Console.WriteLine( factorial.Compile( )( num ) );
- }
-
- static void LambdaTest( int num ) {
-
- Expression<SelfApplicableExpr<
- Expression<Func<
- Expression<Func<
- Expression<Func<int, int>>,
- Expression<Func<int, int>>
- >>,
- Expression<Func<int, int>>
- >>
- >> Y =
- y => f => x => f.Compile( )( y.Compile( )( y ).Compile( )( f ) ).Compile( )( x );
-
-
- Expression<Func<
- Expression<Func<
- Expression<Func<int, int>>,
- Expression<Func<int, int>>
- >>,
- Expression<Func<int, int>>
- >> Fix = Y.Compile( )( Y );
-
-
-
- Expression<Func<
- Expression<Func<int, int>>,
- Expression<Func<int, int>>
- >> F = fac => x => x == 0 ? 1 : x * fac.Compile( )( x - 1 );
-
-
- Expression<Func<int, int>> factorial = Fix.Compile( )( F );
-
-
-
- Console.WriteLine( factorial.Compile( )( num ) );
- }
- }
using System;using System.Linq.Expressions;static class TestRecursiveExpressionTree {static void Main( string[ ] args ) {ExpressionTreeTest( 5 ); // prints 120LambdaTest( 4 ); // prints 24}delegate T SelfApplicableExpr<T>( Expression<SelfApplicableExpr<T>> self );static void ExpressionTreeTest( int num ) {// parametersvar y = Expression.Parameter(typeof(Expression<SelfApplicableExpr<Expression<Func<Expression<Func<Expression<Func<int, int>>,Expression<Func<int, int>>>>,Expression<Func<int, int>>>>>>),"y");var f = Expression.Parameter(typeof(Expression<Func<Expression<Func<int, int>>,Expression<Func<int, int>>>>),"f");var x = Expression.Parameter( typeof( int ), "x" );var fac = Expression.Parameter( typeof( Expression<Func<int, int>> ), "fac" );// Y Combinatorvar Y = Expression.Lambda<SelfApplicableExpr<Expression<Func<Expression<Func<Expression<Func<int, int>>,Expression<Func<int, int>>>>,Expression<Func<int, int>>>>>>(Expression.Quote(Expression.Lambda<Func<Expression<Func<Expression<Func<int, int>>,Expression<Func<int, int>>>>,Expression<Func<int, int>>>>(Expression.Quote(Expression.Lambda<Func<int, int>>(Expression.Invoke(Expression.Call(Expression.Invoke(Expression.Call(f,typeof(Expression<Func<Expression<Func<int, int>>,Expression<Func<int, int>>>>).GetMethod( "Compile" ),new Expression[ ] { }),new Expression[ ] {Expression.Invoke(Expression.Call(Expression.Invoke(Expression.Call(y,typeof(Expression<SelfApplicableExpr<Expression<Func<Expression<Func<Expression<Func<int, int>>,Expression<Func<int, int>>>>,Expression<Func<int, int>>>>>>).GetMethod( "Compile" ),new Expression[ ] { }),new Expression[ ] { y }),typeof(Expression<Func<Expression<Func<Expression<Func<int, int>>,Expression<Func<int, int>>>>,Expression<Func<int, int>>>>).GetMethod( "Compile" ),new Expression[ ] { }),new Expression[ ] { f })}),typeof(Expression<Func<int, int>>).GetMethod( "Compile" ),new Expression[ ] { }),new Expression[ ] { x }),new ParameterExpression[ ] { x })),new ParameterExpression[ ] { f })),new ParameterExpression[ ] { y });// Fix-point findervar Fix = Y.Compile( )( Y );// Factorial helpervar F = Expression.Lambda<Func<Expression<Func<int, int>>,Expression<Func<int, int>>>>(Expression.Quote(Expression.Lambda<Func<int, int>>(Expression.Condition(Expression.Equal( // testx,Expression.Constant(0,typeof( int ))),Expression.Constant( // if true1,typeof( int )),Expression.Multiply( // if falsex,Expression.Invoke(Expression.Call(fac,typeof( Expression<Func<int, int>> ).GetMethod( "Compile" ),new Expression[ ] { }),new Expression[ ] {Expression.Subtract(x,Expression.Constant(1,typeof( int )))}))),new ParameterExpression[ ] { x })),new ParameterExpression[ ] { fac });// Factorial expression treevar factorial = Fix.Compile( )( F );// call the expression treeConsole.WriteLine( factorial.Compile( )( num ) );}static void LambdaTest( int num ) {// Y CombinatorExpression<SelfApplicableExpr<Expression<Func<Expression<Func<Expression<Func<int, int>>,Expression<Func<int, int>>>>,Expression<Func<int, int>>>>>> Y =y => f => x => f.Compile( )( y.Compile( )( y ).Compile( )( f ) ).Compile( )( x );// Fix-point finderExpression<Func<Expression<Func<Expression<Func<int, int>>,Expression<Func<int, int>>>>,Expression<Func<int, int>>>> Fix = Y.Compile( )( Y );// or just: var Fix = Y.Compile( )( Y );// Factorial helperExpression<Func<Expression<Func<int, int>>,Expression<Func<int, int>>>> F = fac => x => x == 0 ? 1 : x * fac.Compile( )( x - 1 );// Factorial expression treeExpression<Func<int, int>> factorial = Fix.Compile( )( F );// or just: var factorial = Fix.Compile( )( F );// call the expression treeConsole.WriteLine( factorial.Compile( )( num ) );}}上面的例子里,前一個(gè)版本的Expression tree是手工創(chuàng )建的,后一個(gè)版本的是由編譯器從lambda表達式轉換來(lái)的。原理另外再解釋?zhuān)信d趣的話(huà)先運行上面的例子看看吧。
上面這個(gè)例子如果把整個(gè)遞歸函數寫(xiě)在一個(gè)lambda表達式里的話(huà),類(lèi)型和內容都會(huì )簡(jiǎn)單些,不用Compile()那么多次……嘛,不管了,總之現在這樣也能運行。
再看順序結構的模擬既然能模擬出循環(huán)的效果,讓我們回頭看看前面的一個(gè)例子:
- ( ) =>
- ( ( Func<string, object> ) ( s => (
- ( ( Action ) ( ( ) => Console.WriteLine( s.ToLower( ) ) ) ).DynamicInvoke( ) ??
- ( ( Action ) ( ( ) => Console.WriteLine( s.ToUpper( ) ) ) ).DynamicInvoke( ) )
- ) )( Console.ReadLine( ) )
( ) =>( ( Func<string, object> ) ( s => (( ( Action ) ( ( ) => Console.WriteLine( s.ToLower( ) ) ) ).DynamicInvoke( ) ??( ( Action ) ( ( ) => Console.WriteLine( s.ToUpper( ) ) ) ).DynamicInvoke( ) )) )( Console.ReadLine( ) )
很明顯,DynamicInvoke()與??的包裝部分是重復的。能把它抽象出來(lái)么?
為了方便,先定義一個(gè)輔助類(lèi):
- delegate T SelfApplicable<T>( SelfApplicable<T> func );
-
- public static class Functional<T1, T2, TR> {
- private static readonly
- SelfApplicable<Func<Func<Func<T1, T2, TR>, Func<T1, T2, TR>>, Func<T1, T2, TR>>>
- _yCombinator =
- y => f => ( a, b ) => f( y( y )( f ) )( a, b );
- private static readonly
- Func<Func<Func<T1, T2, TR>, Func<T1, T2, TR>>, Func<T1, T2, TR>>
- _fixPointGenerator =
- _yCombinator( _yCombinator );
-
- public static Func<Func<Func<T1, T2, TR>, Func<T1, T2, TR>>, Func<T1, T2, TR>> Fix {
- get { return _fixPointGenerator; }
- }
- }
delegate T SelfApplicable<T>( SelfApplicable<T> func );public static class Functional<T1, T2, TR> {private static readonlySelfApplicable<Func<Func<Func<T1, T2, TR>, Func<T1, T2, TR>>, Func<T1, T2, TR>>>_yCombinator =y => f => ( a, b ) => f( y( y )( f ) )( a, b );private static readonlyFunc<Func<Func<T1, T2, TR>, Func<T1, T2, TR>>, Func<T1, T2, TR>>_fixPointGenerator =_yCombinator( _yCombinator );public static Func<Func<Func<T1, T2, TR>, Func<T1, T2, TR>>, Func<T1, T2, TR>> Fix {get { return _fixPointGenerator; }}}可以看出這個(gè)Functional<T1,T2,TR>跟前面的Functional<T,TR>內容幾乎是一樣的(也就是Y組合子的實(shí)現而已,見(jiàn)過(guò)“(Y F) = (F (YF))”么?),只是參數的個(gè)數不一樣而已;很可惜這種變化很難進(jìn)一步封裝,只能用到多少個(gè)參數的時(shí)候就定義一個(gè)對應的泛型類(lèi)。
然后通過(guò)lambda表達式定義一個(gè)輔助用的委托:
- Func<Func<Action[ ], int, object>, Func<Action[ ], int, object>> invokeAll =
- invokeIter => ( actions, index ) =>
- index < actions.Length ?
- actions[ index ].DynamicInvoke( ) ?? invokeIter( actions, index + 1 ) :
- null;
Func<Func<Action[ ], int, object>, Func<Action[ ], int, object>> invokeAll =invokeIter => ( actions, index ) =>index < actions.Length ?actions[ index ].DynamicInvoke( ) ?? invokeIter( actions, index + 1 ) :null;
這樣我們就能把下面這個(gè)帶有語(yǔ)句塊的lambda表達式:
- ( int x, int y ) => {
- Console.WriteLine( "x = {0}", x );
- Console.WriteLine( "y = {0}", y );
- Console.WriteLine( "x+y = {0}", x + y );
- }
( int x, int y ) => {Console.WriteLine( "x = {0}", x );Console.WriteLine( "y = {0}", y );Console.WriteLine( "x+y = {0}", x + y );}轉換成這樣:
- ( x, y ) =>
- Functional<Action[ ], int, object>.Fix( invokeAll )( new Action[ ] {
- ( ) => Console.WriteLine( "x = {0}", x ),
- ( ) => Console.WriteLine( "y = {0}", y ),
- ( ) => Console.WriteLine( "x+y = {0}", x + y )
- }, 0 )
( x, y ) =>Functional<Action[ ], int, object>.Fix( invokeAll )( new Action[ ] {( ) => Console.WriteLine( "x = {0}", x ),( ) => Console.WriteLine( "y = {0}", y ),( ) => Console.WriteLine( "x+y = {0}", x + y )}, 0 )當然還有辦法進(jìn)一步抽象,不過(guò)這個(gè)樣子已經(jīng)比前面的版本要簡(jiǎn)潔了,不是么?
順帶一提,把那些輔助類(lèi)和委托的有效部分直接寫(xiě)在這個(gè)lambda表達式里也可以,不過(guò)這里為了代碼簡(jiǎn)潔還是分開(kāi)寫(xiě)了。如果把所有東西合在一起的話(huà)……(SelfApplicable的定義還是得在別處聲明)
- Expression<Action<int, int>> expr = ( arg1, arg2 ) =>
- ( ( SelfApplicable<
- Func<
- Func<
- Func<Action[ ], int, object>,
- Func<Action[ ], int, object>
- >,
- Func<Action[ ], int, object>>> ) (
- y => f => ( a, b ) => f( y( y )( f ) )( a, b ) )
- )( y => f => ( a, b ) => f( y( y )( f ) )( a, b )
- )( invokeIter => ( actions, index ) =>
- index < actions.Length ?
- actions[ index ].DynamicInvoke( ) ?? invokeIter( actions, index + 1 ) :
- null
- )( new Action[ ] {
- ( ) => Console.WriteLine( "x = {0}", arg1 ),
- ( ) => Console.WriteLine( "y = {0}", arg2 ),
- ( ) => Console.WriteLine( "x+y = {0}", arg1 + arg2 )
- }, 0 );
Expression<Action<int, int>> expr = ( arg1, arg2 ) =>( ( SelfApplicable<Func<Func<Func<Action[ ], int, object>,Func<Action[ ], int, object>>,Func<Action[ ], int, object>>> ) (y => f => ( a, b ) => f( y( y )( f ) )( a, b ) ))( y => f => ( a, b ) => f( y( y )( f ) )( a, b ))( invokeIter => ( actions, index ) =>index < actions.Length ?actions[ index ].DynamicInvoke( ) ?? invokeIter( actions, index + 1 ) :null)( new Action[ ] {( ) => Console.WriteLine( "x = {0}", arg1 ),( ) => Console.WriteLine( "y = {0}", arg2 ),( ) => Console.WriteLine( "x+y = {0}", arg1 + arg2 )}, 0 );(順便賦值給Expression<Action<int,int>>來(lái)讓編譯器生成Expression tree。很明顯把幾個(gè)lambda表達式合成一個(gè)之后,要顯式指定的類(lèi)型就沒(méi)那么多了,都集中在開(kāi)始的那個(gè)類(lèi)型轉換上。)
這樣調用就行:
expr.Compile( )( 1, 2 );// x = 1// y = 2// x+y = 3
敬請期待后續文章。
Have fun with Expression trees!