LINQ与DLR的Expression tree(5):用lambda表达式表示常见控制结构

LINQ Expression tree能直接表示的语法结构在上一篇中已经详细的列了出来,包括算术、按位、逻辑等运算符的表达式、条件表达式、方法调用表达式、对象创建表达式等。要表示一般的命令式程序,需要支持3中基本控制流语法结构:顺序、分支、循环。在C#中这三种结构能很自然的用语句来表示,但有语句的lambda表达式无法用LINQ Expression tree表示。下面就让我们来看看这三种结构如何转换成只用表达式的方式来表示。 

需要事先说明三点: 
1、只要避免使用赋值相关的表达式,一个只包含表达式的lambda表达式既可以转换为委托也可以转换为Expression tree;手工创建Expression tree比较麻烦,本文的例子无特别说明都将通过转换为委托的lambda表达式来表示,转换为Expression tree的版本留待后续文章再详细给出。 
2、能够用Expression tree表示的东西不一定能被所有LINQ provider接受。这点必须注意。也就是说即使能把一个语句块变成一个表达式,也未必能用在LINQ to SQL等场景里。 
3、下面演示的转换方式绝对不代表任何best practice;.NET中方法调用的开销是不可忽视的,因而转换后的表达式肯定会比原本的语句慢。本文只是着重说明转换的可能性。 

局部变量的模拟 

局部变量,顾名思义,在方法外是看不到的;方法调用结束后局部变量也就随即消失(如果不涉及闭包)。它们的作用主要是保存一些中间的计算结果,将很长的表达式分割成一些比较短的表达式,使得程序代码更为清晰。一般在C#中使用局部变量需要声明语句和赋值表达式,而这两种结构LINQ Expression tree都无法支持。怎么办呢? 

许多情况下可以把局部变量去掉,把短表达式拼接回到长的表达式,避免使用语句。一个简单的例子: 

C#代码  收藏代码
  1. int x, int y ) => {  
  2.     var strX = x.ToString( "X" );  
  3.     var strY = y.ToString( "x" );  
  4.     Console.WriteLine( "x = {0}, y = {1}", strX, strY );  
  5. }  


可以很顺利的变成: 

C#代码  收藏代码
  1. int x, int y ) =>  
  2.     Console.WriteLine( "x = {0}, y = {1}", x.ToString( "X" ), y.ToString( "x" ) )  



但这是不是唯一的选择呢?或许计算某个中间结果的开销很大,而后面的计算中要多次用到它;或许计算某个中间结果会对产生副作用,而我们只希望副作用发生一次;有许多情况下我们还是需要局部变量的。怎么办呢? 

注意到,LINQ Expression tree虽然不支持声明局部变量,却允许声明和调用嵌套的lambda表达式。将中间的计算结果以参数的形式传给一个嵌套的lambda表达式,也就等同于使用了局部变量。例子: 

C#代码  收藏代码
  1. ( ) => {  
  2.     var input = Console.ReadLine( );  
  3.     var lowerCase = input.ToLower( );  
  4.     var upperCase = input.ToUpper( );  
  5.     Console.WriteLine( "lower case: {0}\nupper case: {1}", lowerCase, upperCase );  
  6. }  


变成: 

C#代码  收藏代码
  1. ( ) => ( ( Action<string> ) ( s =>  
  2.         Console.WriteLine( "lower case: {0}\nupper case: {1}",  
  3.             s.ToLower( ), s.ToUpper( ) )  
  4.     ) )( Console.ReadLine( ) )  


注意到Console.ReadLine()是有副作用的,这个方法调用会消耗掉输入流里的一行,显然我们不希望调用它两次。 
将语句转换为表达式之后,原本写在前面的语句变成写在后面的表达式了,不习惯的话看起来或许很不自然,但注意到C#采用了严格求值,要先把参数的值求出来之后才执行方法调用,所以转换后的写法的执行顺序与转换前是一致的。 
例子中的强制类型转换((Action<string>))是必要的,编译器需要它才知道该生成委托还是Expression tree。实际上只要最外层的lambda表达式是赋值给Expression<TDelegate>类型的变量,整个lambda表达式(包括内层嵌套的)都会生成为Expression tree,像这样: 

C#代码  收藏代码
  1. Expression<Action> expr = ( ) => ( ( Action<string> ) ( s =>  
  2.     Console.WriteLine( "lower case: {0}\nupper case: {1}",  
  3.         s.ToLower( ), s.ToUpper( ) )  
  4. ) )( Console.ReadLine( ) );  


会由编译器生成为: 

C#代码  收藏代码
  1. var s = Expression.Parameter( typeofstring ), "s" );  
  2. Expression<Action> expr = Expression.Lambda<Action>(  
  3.     Expression.Invoke(  
  4.         Expression.Convert(  
  5.             Expression.Lambda<Action<string>>(  
  6.                 Expression.Call(  
  7.                     null,  
  8.                     typeof( Console ).GetMethod(  
  9.                         "WriteLine",  
  10.                         new Type[ ] {  
  11.                             typeofstring ),  
  12.                             typeofobject ),  
  13.                             typeofobject )  
  14.                         }  
  15.                     ),  
  16.                     new Expression[ ] {  
  17.                         Expression.Constant(  
  18.                             "lower case: {0}\nupper case: {1}",  
  19.                             typeofstring )  
  20.                         ),  
  21.                         Expression.Call(  
  22.                             s,  
  23.                             typeofstring ).GetMethod( "ToLower", Type.EmptyTypes ),  
  24.                             new Expression[ 0 ]  
  25.                         ),  
  26.                         Expression.Call(  
  27.                             s,  
  28.                             typeofstring ).GetMethod( "ToUpper", Type.EmptyTypes ),  
  29.                             new Expression[ 0 ]  
  30.                         )  
  31.                     }  
  32.                 ),  
  33.                 new [ ] { s }  
  34.             ),  
  35.             typeof( Action<string> )  
  36.         ),  
  37.         new Expression[] {  
  38.             Expression.Call(  
  39.                 null,  
  40.                 typeof( Console ).GetMethod( "ReadLine", Type.EmptyTypes ),  
  41.                 new Expression[ 0 ]  
  42.             )  
  43.         }  
  44.     ),  
  45.     new ParameterExpression[ 0 ]  
  46. );  


注意到里面嵌套的lambda表达式也被编译为Expression tree而不是一个委托。如果手工创建这棵Expression tree则可以省去类型转换的那步,也就是等同于将上述代码的第4、35、36这三行注释掉,因为那个类型转换本身并没有意义,只是为了让编译器得到足够的类型信息来转换lambda表达式而已。 

顺序结构的模拟 

语句与表达式最大的区别就是前者一般没有值或忽略值,而后者一定有值。因而语句只能通过分隔符一个个按顺序写,而不能像表达式可以用运算符结合起来或作为参数传递。在用表达式模拟顺序结构时,最大的障碍就是返回值类型为void的方法——它们不返回任何值,无法放在表达式里与表达式的其它部分组合起来。如果能有办法把void变成null也好……所谓“遇到问题的时候只要增加一个间接层就能解决” 

而.NET就提供了这么一种办法。所有委托的基类,Delegate类上有一个DynamicInvoke()方法,参数类型是params object[],返回值类型是object,正好满足我们的需要。于是像Console.WriteLine()这种返回void的方法,可以包装为一个lambda表达式,然后用DynamicInvoke()包装起来: 

C#代码  收藏代码
  1. ( ( Action ) ( ( ) => Console.WriteLine( ) ) ).DynamicInvoke( )  


这样的调用将总是返回null,因为Action系的委托忽略返回值,而Delegate.DynamicInvoke()对返回值类型为void的委托类型总是返回null。这样就有机会用到C#里方便的??运算符把语句变成表达式串起来了——??运算符是一个具有短路求值性质的运算符,语义是:当左操作数不为null时值为左操作数的值,否则为右操作数的值。举例来说,把这两个语句: 

C#代码  收藏代码
  1. Console.WriteLine( "1" );  
  2. Console.WriteLine( "2" );  


变成: 

C#代码  收藏代码
  1. ( ( Action ) ( ( ) => Console.WriteLine( "1" ) ) ).DynamicInvoke( ) ??  
  2. ( ( Action ) ( ( ) => Console.WriteLine( "2" ) ) ).DynamicInvoke( )  



把这种转换方式应用在lambda表达式里,就可以把这个带有语句块的lambda表达式: 

C#代码  收藏代码
  1. ( ) => {  
  2.     var s = Console.ReadLine( );  
  3.     Console.WriteLine( s.ToLower( ) );  
  4.     Console.WriteLine( s.ToUpper( ) );  
  5. }  


(注意这里的s是一个局部变量) 
变成一个只有表达式的lambda表达式: 

C#代码  收藏代码
  1. ( ) =>  
  2.     ( ( Func<stringobject> ) ( s => (  
  3.         ( ( Action ) ( ( ) => Console.WriteLine( s.ToLower( ) ) ) ).DynamicInvoke( ) ??  
  4.         ( ( Action ) ( ( ) => Console.WriteLine( s.ToUpper( ) ) ) ).DynamicInvoke( ) )  
  5.     ) )( Console.ReadLine( ) )  


这个lambda表达式就能够被LINQ Expression tree所表示了。 
留意一下这个例子里的s,在最内层的lambda表达式里是不是参数也不是局部变量,而是自由变量,体现了C#的lambda表达式具有闭包的功能。如果没有闭包的功能,s就得出现在每个最内层lambda表达式的参数表里,那就麻烦了。 
把这里闭包的实现简单的说明一下。微软的C# 3.0编译器在遇到使用了自由变量的lambda表达式时,会根据赋值目标是委托还是Expression<TDelegate>来决定如何处理这些自由变量。当赋值目标是委托时,被用到的自由变量会被“提升”(lift)为一个编译器生成的匿名类的成员变量,而lambda表达式则对应提升为那个类的成员方法。当赋值目标是Expression<TDelegate>时,C# 3.0编译器并不会做什么特别的处理,就照样把Expression tree生成出来;但在调用Expression<TDelegate>.Compile()时,负责编译expression tree的内部编译器会检查是否存在自由变量,存在的话同样会将自由变量“提升”;不过实现细节与C# 3.0编译器的不同,可以看到带有自由变量的expression tree编译出来的委托的参数列表会比原本指定的TDelegate多了一个参数,ExecutionScope(.NET Framework 3.5: System.Runtime.CompilerServices)或者CompilerScope(.NET Framework 4.0)。对此有兴趣的,可以到DLR的官网下载一份DLR的源码来阅读,里面的Microsoft.Scripting.Core里就有LambdaCompiler的实现,也就是将会用在.NET Framework 4.0里的实现。 

分支结构的模拟 

最基本的分支结构是if-else语句,对应的有?:运算符表示的条件表达式。这两者看似能直接对应,但语句与表达式的区别再次体现了出来:语句没有或忽略值,而表达式必须有值。因而,if语句可以没有else子句,也不关心返回值的问题;但条件表达式必须同时有true和false的分支,且两个分支的值的类型必须匹配。举例来说: 

C#代码  收藏代码
  1. static int Abs( int x ) {  
  2.     if ( 0 > x ) return -x;  
  3.     else return x;  
  4. }  


很容易变为表达式: 

C#代码  收藏代码
  1. int x ) => ( 0 > x ) ? -x : x  


但这个函数就无法直接表示为表达式了: 

C#代码  收藏代码
  1. static void PrintIfEven( int x ) {  
  2.     if ( 0 == x % 2 ) Console.WriteLine( x );  
  3. }  


要转换,就需要应用到上一节提到的包装技巧: 

C#代码  收藏代码
  1. int x ) => ( 0 == x % 2 ) ?  
  2.     ( ( Action ) ( ( ) => Console.WriteLine( x ) ) ).DynamicInvoke( ) :  
  3.     null  


再次注意到闭包的应用:最内层的lambda表达式使用了自由变量x。 

switch语句可以看作是if-else串起来,转换为表达式的话,串联使用条件表达式就行,有需要的时候加上包装。例如: 

C#代码  收藏代码
  1. static string Grade( int point ) {  
  2.     switch ( point / 10 ) {  
  3.     case 10:  
  4.     case 9:  
  5.         return "A";  
  6.     case 8:  
  7.         return "B";  
  8.     case 7:  
  9.         return "C";  
  10.     case 6:  
  11.         return "D";  
  12.     default:  
  13.         return "F";  
  14.     }  
  15. }  


简单的变成: 

C#代码  收藏代码
  1. int point ) => ( ( Func<intstring> ) ( i =>  
  2.     ( 9 == i || 10 == i ) ? "A" :  
  3.     ( 8 == i ) ? "B" :  
  4.     ( 7 == i ) ? "C" :  
  5.     ( 6 == i ) ? "D" :  
  6.     /* else */   "F"  



循环结构的模拟 

用命令式语言的思维方式,循环语句无法转换为表达式。但我们可以一步步来寻找突破口。 
首先找出循环本质上需要怎样的支持。有两点:1、需要有终止条件;2、需要能够在一轮循环结束后让控制流跳转回到循环体的开始处。另外,大多循环还需要用到循环变量,要能够更新循环变量的值。 
假如不能使用局部变量来做循环变量,回想到本文开头提到的转换局部变量的方法,可以把循环变量放在参数里,把循环体变成一个函数。这样,循环就变成递归的形式了,这也正好解决了控制流跳转的问题。事实上在函数式语言里,迭代(iteration)就是用这样的递归来实现的。或者,从计算模型理论来说,循环不过是递归的一种语法糖而已;从函数的角度来看,递归才是本质性的概念。 
于是,如何把循环语句转换为表达式的问题就变成了如何用lambda表达式表示递归函数的问题。问题是,lambda表达式没有名字——它就是匿名函数。没有名字的话,如何递归呢?肯定会有人想到这种做法: 

C#代码  收藏代码
  1. Func<intint> factorial = null;  
  2. factorial = x => ( 0 == x ) ? 1 : x * factorial( x - 1 );  


在C#里这是可行的,但不彻底。这里我们要用的办法,是通过找到一个函数的不动点(fix-point)来实现lambda表达式的递归。具体的原理留待以后的文章再详细讨论,这里先给出结果来满足一下眼球。通过这样一个辅助类:

C#代码  收藏代码
  1. delegate T SelfApplicable<T>( SelfApplicable<T> func );  
  2.   
  3. public static class Functional<T, TR> {  
  4.     private static readonly  
  5.         SelfApplicable<Func<Func<Func<T, TR>, Func<T, TR>>, Func<T, TR>>> _yCombinator =  
  6.             y => f => x => f( y( y )( f ) )( x );  
  7.     private static readonly  
  8.         Func<Func<Func<T, TR>, Func<T, TR>>, Func<T, TR>> _fixPointGenerator =  
  9.             _yCombinator( _yCombinator );  
  10.   
  11.     public static Func<Func<Func<T, TR>, Func<T, TR>>, Func<T, TR>> Fix {  
  12.         get { return _fixPointGenerator; }  
  13.     }  
  14. }  


我们可以做到这种效果: 

C#代码  收藏代码
  1. Func<intint> factorial = Functional<intint>.Fix(  
  2.     fac => x => ( 0 == x ) ? 1 : x * fac( x - 1) );  
  3. factorial( 5 ); // 120  



注意到这种做法只用了lambda表达式,所以也可以转换为使用Expression tree来完成整个过程。把上述辅助类里有效的内容提取出来,我们来看看只用Expression tree实现的版本: 

C#代码  收藏代码
  1. using System;  
  2. using System.Linq.Expressions;  
  3.   
  4. static class TestRecursiveExpressionTree {  
  5.     static void Main( string[ ] args ) {  
  6.         ExpressionTreeTest( 5 ); // prints 120  
  7.         LambdaTest( 4 );         // prints 24  
  8.     }  
  9.   
  10.     delegate T SelfApplicableExpr<T>( Expression<SelfApplicableExpr<T>> self );  
  11.   
  12.     static void ExpressionTreeTest( int num ) {  
  13.         // parameters  
  14.         var y = Expression.Parameter(  
  15.             typeof(  
  16.                 Expression<SelfApplicableExpr<  
  17.                     Expression<Func<  
  18.                         Expression<Func<  
  19.                             Expression<Func<intint>>,  
  20.                             Expression<Func<intint>>  
  21.                         >>,  
  22.                         Expression<Func<intint>>  
  23.                     >>  
  24.                 >>  
  25.             ),  
  26.             "y"  
  27.         );  
  28.         var f = Expression.Parameter(  
  29.             typeof(  
  30.                 Expression<Func<  
  31.                     Expression<Func<intint>>,  
  32.                     Expression<Func<intint>>  
  33.                 >>  
  34.             ),  
  35.             "f"  
  36.         );  
  37.         var x = Expression.Parameter( typeofint ), "x" );  
  38.         var fac = Expression.Parameter( typeof( Expression<Func<intint>> ), "fac" );  
  39.   
  40.         // Y Combinator  
  41.         var Y = Expression.Lambda<SelfApplicableExpr<  
  42.             Expression<Func<  
  43.                 Expression<Func<  
  44.                     Expression<Func<intint>>,  
  45.                     Expression<Func<intint>>  
  46.                 >>,  
  47.                 Expression<Func<intint>>  
  48.             >>  
  49.         >>(  
  50.             Expression.Quote(  
  51.                 Expression.Lambda<  
  52.                     Func<  
  53.                         Expression<Func<  
  54.                             Expression<Func<intint>>,  
  55.                             Expression<Func<intint>>  
  56.                         >>,  
  57.                         Expression<Func<intint>>  
  58.                     >  
  59.                 >(  
  60.                     Expression.Quote(  
  61.                         Expression.Lambda<Func<intint>>(  
  62.                             Expression.Invoke(  
  63.                                 Expression.Call(  
  64.                                     Expression.Invoke(  
  65.                                         Expression.Call(  
  66.                                             f,  
  67.                                             typeof(  
  68.                                                 Expression<Func<  
  69.                                                     Expression<Func<intint>>,  
  70.                                                     Expression<Func<intint>>  
  71.                                                 >>  
  72.                                             ).GetMethod( "Compile" ),  
  73.                                             new Expression[ ] { }  
  74.                                         ),  
  75.                                         new Expression[ ] {  
  76.                                             Expression.Invoke(  
  77.                                                 Expression.Call(  
  78.                                                     Expression.Invoke(  
  79.                                                         Expression.Call(  
  80.                                                             y,  
  81.                                                             typeof(  
  82.                                                                 Expression<SelfApplicableExpr<  
  83.                                                                     Expression<Func<  
  84.                                                                         Expression<Func<  
  85.                                                                             Expression<Func<intint>>,  
  86.                                                                             Expression<Func<intint>>  
  87.                                                                         >>,  
  88.                                                                         Expression<Func<intint>>  
  89.                                                                     >>  
  90.                                                                 >>  
  91.                                                             ).GetMethod( "Compile" ),  
  92.                                                             new Expression[ ] { }  
  93.                                                         ),  
  94.                                                         new Expression[ ] { y }  
  95.                                                     ),  
  96.                                                     typeof(  
  97.                                                         Expression<Func<  
  98.                                                             Expression<Func<  
  99.                                                                 Expression<Func<intint>>,  
  100.                                                                 Expression<Func<intint>>  
  101.                                                             >>,  
  102.                                                             Expression<Func<intint>>  
  103.                                                         >>  
  104.                                                     ).GetMethod( "Compile" ),  
  105.                                                     new Expression[ ] { }  
  106.                                                 ),  
  107.                                                 new Expression[ ] { f }  
  108.                                             )  
  109.                                         }  
  110.                                     ),  
  111.                                     typeof(  
  112.                                         Expression<Func<intint>>  
  113.                                     ).GetMethod( "Compile" ),  
  114.                                     new Expression[ ] { }  
  115.                                 ),  
  116.                                 new Expression[ ] { x }  
  117.                             ),  
  118.                             new ParameterExpression[ ] { x }  
  119.                         )  
  120.                     ),  
  121.                     new ParameterExpression[ ] { f }  
  122.                 )  
  123.             ),  
  124.             new ParameterExpression[ ] { y }  
  125.         );  
  126.   
  127.         // Fix-point finder  
  128.         var Fix = Y.Compile( )( Y );  
  129.   
  130.         // Factorial helper  
  131.         var F = Expression.Lambda<  
  132.             Func<  
  133.                 Expression<Func<intint>>,  
  134.                 Expression<Func<intint>>  
  135.             >  
  136.         >(  
  137.             Expression.Quote(  
  138.                 Expression.Lambda<Func<intint>>(  
  139.                     Expression.Condition(  
  140.                         Expression.Equal(    // test  
  141.                             x,  
  142.                             Expression.Constant(  
  143.                                 0,  
  144.                                 typeofint )  
  145.                             )  
  146.                         ),  
  147.                         Expression.Constant( // if true  
  148.                             1,  
  149.                             typeofint )  
  150.                         ),  
  151.                         Expression.Multiply( // if false  
  152.                             x,  
  153.                             Expression.Invoke(  
  154.                                 Expression.Call(  
  155.                                     fac,  
  156.                                     typeof( Expression<Func<intint>> ).GetMethod( "Compile" ),  
  157.                                     new Expression[ ] { }  
  158.                                 ),  
  159.                                 new Expression[ ] {  
  160.                                     Expression.Subtract(  
  161.                                         x,  
  162.                                         Expression.Constant(  
  163.                                             1,  
  164.                                             typeofint )  
  165.                                         )  
  166.                                     )  
  167.                                 }  
  168.                             )  
  169.                         )  
  170.                     ),  
  171.                     new ParameterExpression[ ] { x }  
  172.                 )  
  173.             ),  
  174.             new ParameterExpression[ ] { fac }  
  175.         );  
  176.   
  177.         // Factorial expression tree  
  178.         var factorial = Fix.Compile( )( F );  
  179.   
  180.         // call the expression tree  
  181.         Console.WriteLine( factorial.Compile( )( num ) );  
  182.     }  
  183.   
  184.     static void LambdaTest( int num ) {  
  185.         // Y Combinator  
  186.         Expression<SelfApplicableExpr<  
  187.             Expression<Func<  
  188.                 Expression<Func<  
  189.                     Expression<Func<intint>>,  
  190.                     Expression<Func<intint>>  
  191.                 >>,  
  192.                 Expression<Func<intint>>  
  193.             >>  
  194.         >> Y =  
  195.             y => f => x => f.Compile( )( y.Compile( )( y ).Compile( )( f ) ).Compile( )( x );  
  196.   
  197.         // Fix-point finder  
  198.         Expression<Func<  
  199.             Expression<Func<  
  200.                 Expression<Func<intint>>,  
  201.                 Expression<Func<intint>>  
  202.             >>,  
  203.             Expression<Func<intint>>  
  204.         >> Fix = Y.Compile( )( Y );  
  205.         // or just: var Fix = Y.Compile( )( Y );  
  206.   
  207.         // Factorial helper  
  208.         Expression<Func<  
  209.             Expression<Func<intint>>,  
  210.             Expression<Func<intint>>  
  211.         >> F = fac => x => x == 0 ? 1 : x * fac.Compile( )( x - 1 );  
  212.   
  213.         // Factorial expression tree  
  214.         Expression<Func<intint>> factorial = Fix.Compile( )( F );  
  215.         // or just: var factorial = Fix.Compile( )( F );  
  216.   
  217.         // call the expression tree  
  218.         Console.WriteLine( factorial.Compile( )( num ) );  
  219.     }  
  220. }  


上面的例子里,前一个版本的Expression tree是手工创建的,后一个版本的是由编译器从lambda表达式转换来的。原理另外再解释,有兴趣的话先运行上面的例子看看吧。 
上面这个例子如果把整个递归函数写在一个lambda表达式里的话,类型和内容都会简单些,不用Compile()那么多次……嘛,不管了,总之现在这样也能运行。 

再看顺序结构的模拟 

既然能模拟出循环的效果,让我们回头看看前面的一个例子: 

C#代码  收藏代码
  1. ( ) =>  
  2.     ( ( Func<stringobject> ) ( s => (  
  3.         ( ( Action ) ( ( ) => Console.WriteLine( s.ToLower( ) ) ) ).DynamicInvoke( ) ??  
  4.         ( ( Action ) ( ( ) => Console.WriteLine( s.ToUpper( ) ) ) ).DynamicInvoke( ) )  
  5.     ) )( Console.ReadLine( ) )  


很明显,DynamicInvoke()与??的包装部分是重复的。能把它抽象出来么? 

为了方便,先定义一个辅助类: 

C#代码  收藏代码
  1. delegate T SelfApplicable<T>( SelfApplicable<T> func );  
  2.   
  3. public static class Functional<T1, T2, TR> {  
  4.     private static readonly  
  5.         SelfApplicable<Func<Func<Func<T1, T2, TR>, Func<T1, T2, TR>>, Func<T1, T2, TR>>>  
  6.             _yCombinator =  
  7.                 y => f => ( a, b ) => f( y( y )( f ) )( a, b );  
  8.     private static readonly  
  9.         Func<Func<Func<T1, T2, TR>, Func<T1, T2, TR>>, Func<T1, T2, TR>>  
  10.             _fixPointGenerator =  
  11.                 _yCombinator( _yCombinator );  
  12.   
  13.     public static Func<Func<Func<T1, T2, TR>, Func<T1, T2, TR>>, Func<T1, T2, TR>> Fix {  
  14.         get { return _fixPointGenerator; }  
  15.     }  
  16. }  


可以看出这个Functional<T1,T2,TR>跟前面的Functional<T,TR>内容几乎是一样的(也就是Y组合子的实现而已,见过“(Y F) = (F (Y F))”么?),只是参数的个数不一样而已;很可惜这种变化很难进一步封装,只能用到多少个参数的时候就定义一个对应的泛型类。 

然后通过lambda表达式定义一个辅助用的委托: 

C#代码  收藏代码
  1. Func<Func<Action[ ], intobject>, Func<Action[ ], intobject>> invokeAll =  
  2.     invokeIter => ( actions, index ) =>  
  3.         index < actions.Length ?  
  4.         actions[ index ].DynamicInvoke( ) ?? invokeIter( actions, index + 1 ) :  
  5.         null;  


这个委托所做的就是遍历一个Action[]并调用其中的每个Action;结合Y组合子使用,其作用类似下面这个for循环:

C#代码  收藏代码
  1. for ( int index = 0; index < actions.Length; ++index ) {  
  2.     actions[ index ].DynamicInvoke( );  
  3. }  



这样我们就能把下面这个带有语句块的lambda表达式: 

C#代码  收藏代码
  1. int x, int y ) => {  
  2.     Console.WriteLine( "x = {0}", x );  
  3.     Console.WriteLine( "y = {0}", y );  
  4.     Console.WriteLine( "x+y = {0}", x + y );  
  5. }  


转换成这样: 

C#代码  收藏代码
  1. ( x, y ) =>  
  2.     Functional<Action[ ], intobject>.Fix( invokeAll )( new Action[ ] {  
  3.         ( ) => Console.WriteLine( "x = {0}", x ),  
  4.         ( ) => Console.WriteLine( "y = {0}", y ),  
  5.         ( ) => Console.WriteLine( "x+y = {0}", x + y )  
  6.     }, 0 )  


当然还有办法进一步抽象,不过这个样子已经比前面的版本要简洁了,不是么? 
顺带一提,把那些辅助类和委托的有效部分直接写在这个lambda表达式里也可以,不过这里为了代码简洁还是分开写了。如果把所有东西合在一起的话……(SelfApplicable的定义还是得在别处声明) 

C#代码  收藏代码
  1. Expression<Action<intint>> expr = ( arg1, arg2 ) =>  
  2.     ( ( SelfApplicable<  
  3.             Func<  
  4.                 Func<  
  5.                     Func<Action[ ], intobject>,  
  6.                     Func<Action[ ], intobject>  
  7.                 >,  
  8.                 Func<Action[ ], intobject>>> ) (  
  9.         y => f => ( a, b ) => f( y( y )( f ) )( a, b ) )  
  10.     )( y => f => ( a, b ) => f( y( y )( f ) )( a, b )  
  11.     )( invokeIter => ( actions, index ) =>  
  12.         index < actions.Length ?  
  13.         actions[ index ].DynamicInvoke( ) ?? invokeIter( actions, index + 1 ) :  
  14.         null  
  15.     )( new Action[ ] {  
  16.         ( ) => Console.WriteLine( "x = {0}", arg1 ),  
  17.         ( ) => Console.WriteLine( "y = {0}", arg2 ),  
  18.         ( ) => Console.WriteLine( "x+y = {0}", arg1 + arg2 )  
  19.     }, 0 );  


(顺便赋值给Expression<Action<int,int>>来让编译器生成Expression tree。很明显把几个lambda表达式合成一个之后,要显式指定的类型就没那么多了,都集中在开始的那个类型转换上。) 
这样调用就行: 

C#代码  收藏代码
  1. expr.Compile( )( 1, 2 );  
  2. // x = 1  
  3. // y = 2  
  4. // x+y = 3  



要是上面给绕得太晕了,那还是直接在一个可以编译执行的文件上玩玩吧: 
把前一节里的例子稍微简化一下合在一起就是这样一个文件: 

C#代码  收藏代码
  1. using System;  
  2. using System.Linq.Expressions;  
  3.   
  4. delegate T SelfApplicable<T>( SelfApplicable<T> func );  
  5.   
  6. sealed class Program {  
  7.     static void Main( string[ ] args ) {  
  8.         Expression<Action<intint>> expr = ( m, n ) =>  
  9.             ( ( Func<  
  10.                     SelfApplicable<  
  11.                         Func<  
  12.                             Func<  
  13.                                 Func<Action[ ], intobject>,  
  14.                                 Func<Action[ ], intobject>  
  15.                             >,  
  16.                             Func<Action[ ], intobject>>>,  
  17.                     Func<  
  18.                         Func<  
  19.                             Func<Action[ ], intobject>,  
  20.                             Func<Action[ ], intobject>  
  21.                         >,  
  22.                         Func<Action[ ], intobject>>> ) (  
  23.                 y => y( y ) )  
  24.             )( y => f => ( a, b ) => f( y( y )( f ) )( a, b )  
  25.             )( invokeIter => ( actions, index ) =>  
  26.                 index < actions.Length ?  
  27.                 actions[ index ].DynamicInvoke( ) ?? invokeIter( actions, index + 1 ) :  
  28.                 null  
  29.             )( new Action[ ] {  
  30.   
  31.                 // NOTE:  
  32.                 // Begin of statement block  
  33.   
  34.                 ( ) => Console.WriteLine( "m = {0}", m ),  
  35.                 ( ) => Console.WriteLine( "n = {0}", n ),  
  36.                 ( ) => Console.WriteLine( "m+n = {0}", m + n )  
  37.   
  38.                 // End of statement block  
  39.   
  40.             }, 0 );  
  41.   
  42.         expr.Compile( )( 1, 2 );  
  43.     }  
  44. }  


注意到一个细微的区别:我把Y组合子的实现稍微改了下,所以lambda表达式看起来简洁了一些,但相对的,前面的类型转换部分则变长了一些。 

嗯相应的手工构造Expression tree的版本: 

C#代码  收藏代码
  1. using System;  
  2. using System.Linq.Expressions;  
  3.   
  4. delegate T SelfApplicable<T>( SelfApplicable<T> func );  
  5.   
  6. static class Program {  
  7.   private static void Main(string[] args) {  
  8.     var mParam              = Expression.Parameter(typeof(int), "m");  
  9.     var nParam              = Expression.Parameter(typeof(int), "n");  
  10.     var yParam              = Expression.Parameter(typeof(SelfApplicable<Func<Func<Func<Action[], intobject>, Func<Action[], intobject>>, Func<Action[], intobject>>>), "y");  
  11.     var fParam              = Expression.Parameter(typeof(Func<Func<Action[], intobject>, Func<Action[], intobject>>), "f");  
  12.     var aParam              = Expression.Parameter(typeof(Action[]), "a");  
  13.     var bParam              = Expression.Parameter(typeof(int), "b");  
  14.     var indexParam          = Expression.Parameter(typeof(int), "index");  
  15.     var actionsParam        = Expression.Parameter(typeof(Action[]), "actions");  
  16.     var invokeIterParam     = Expression.Parameter(typeof(Func<Action[], intobject>), "invokeIter");  
  17.     
  18.     var dynamicInvokeMethod = typeof(Delegate).GetMethod("DynamicInvoke");  
  19.     var writeLineMethod     = typeof(Console).GetMethod("WriteLine"new Type[] { typeof(string), typeof(object)});  
  20.       
  21.     var expr =  
  22.       Expression.Lambda<Action<intint>>(  
  23.         Expression.Invoke(  
  24.           Expression.Invoke(  
  25.             Expression.Invoke(  
  26.               Expression.Lambda<Func<SelfApplicable<Func<Func<Func<Action[], intobject>, Func<Action[], intobject>>, Func<Action[], intobject>>>, Func<Func<Func<Action[], intobject>, Func<Action[], intobject>>, Func<Action[], intobject>>>>(  
  27.                 Expression.Invoke(  
  28.                   yParam,  
  29.                   new Expression[] { yParam }  
  30.                 ),  
  31.                 new ParameterExpression[] { yParam }  
  32.               ),  
  33.               new Expression[] {  
  34.                 Expression.Lambda<SelfApplicable<Func<Func<Func<Action[], intobject>, Func<Action[], intobject>>, Func<Action[], intobject>>>>(  
  35.                   Expression.Lambda<Func<Func<Func<Action[], intobject>, Func<Action[], intobject>>, Func<Action[], intobject>>>(  
  36.                     Expression.Lambda<Func<Action[], intobject>>(  
  37.                       Expression.Invoke(  
  38.                         Expression.Invoke(  
  39.                           fParam,  
  40.                           new Expression[] {  
  41.                             Expression.Invoke(  
  42.                               Expression.Invoke( // invoke the Y-combinator  
  43.                                 yParam,  
  44.                                 new Expression[] { yParam }  
  45.                               ),  
  46.                               new Expression[] { fParam }  
  47.                             )  
  48.                           }  
  49.                         ),  
  50.                         new Expression[] { aParam, bParam }  
  51.                       ),  
  52.                       new ParameterExpression[] { aParam, bParam }  
  53.                     ),  
  54.                     new ParameterExpression[] { fParam }  
  55.                   ),  
  56.                   new ParameterExpression[] { yParam }  
  57.                 )  
  58.               }  
  59.             ),  
  60.             new Expression[] {  
  61.               Expression.Lambda<Func<Func<Action[], intobject>, Func<Action[], intobject>>>(  
  62.                 Expression.Lambda<Func<Action[], intobject>>(  
  63.                   Expression.Condition(  
  64.                     Expression.LessThan(  
  65.                       indexParam,  
  66.                       Expression.ArrayLength(  
  67.                         actionsParam  
  68.                       )  
  69.                     ),  
  70.                     Expression.Coalesce(  
  71.                       Expression.Call(  
  72.                         Expression.ArrayIndex(  
  73.                           actionsParam,  
  74.                           indexParam  
  75.                         ),  
  76.                         dynamicInvokeMethod,  
  77.                         new Expression[] {  
  78.                           Expression.NewArrayInit(  
  79.                             typeof(object),  
  80.                             new Expression[0]  
  81.                           )  
  82.                         }  
  83.                       ),  
  84.                       Expression.Invoke(  
  85.                         invokeIterParam,  
  86.                         new Expression[] {  
  87.                           actionsParam,  
  88.                           Expression.Add(  
  89.                             indexParam,  
  90.                             Expression.Constant(  
  91.                               1,  
  92.                               typeof(int)  
  93.                             )  
  94.                           )  
  95.                         }  
  96.                       )  
  97.                     ),  
  98.                     Expression.Constant(  
  99.                       null,  
  100.                       typeof(object)  
  101.                     )  
  102.                   ),  
  103.                   new ParameterExpression[] { actionsParam, indexParam }  
  104.                 ),  
  105.                 new ParameterExpression[] { invokeIterParam }  
  106.               )  
  107.             }  
  108.           ),  
  109.           new Expression[] {  
  110.             Expression.NewArrayInit(  
  111.               typeof(Action),  
  112.               new Expression[] {  
  113.                 Expression.Lambda<Action>(  
  114.                   Expression.Call(  
  115.                     null,   
  116.                     writeLineMethod,  
  117.                     new Expression[] {  
  118.                       Expression.Constant(  
  119.                         "m = {0}",  
  120.                         typeof(string)  
  121.                       ),  
  122.                       Expression.Convert(  
  123.                         mParam,  
  124.                         typeof(object)  
  125.                       )  
  126.                     }  
  127.                   ),  
  128.                   new ParameterExpression[0]  
  129.                 ),  
  130.                 Expression.Lambda<Action>(  
  131.                   Expression.Call(  
  132.                     null,  
  133.                     writeLineMethod,  
  134.                     new Expression[] {  
  135.                       Expression.Constant(  
  136.                         "n = {0}",  
  137.                         typeof(string)  
  138.                       ),  
  139.                       Expression.Convert(  
  140.                         nParam,  
  141.                         typeof(object)  
  142.                       )  
  143.                     }  
  144.                   ),  
  145.                   new ParameterExpression[0]  
  146.                 ),  
  147.                 Expression.Lambda<Action>(  
  148.                   Expression.Call(  
  149.                     null,  
  150.                     writeLineMethod,  
  151.                     new Expression[] {  
  152.                       Expression.Constant(  
  153.                         "m+n = {0}",  
  154.                         typeof(string)  
  155.                       ),  
  156.                       Expression.Convert(  
  157.                         Expression.Add(  
  158.                           mParam,  
  159.                           nParam  
  160.                         ),  
  161.                         typeof(object)  
  162.                       )  
  163.                     }  
  164.                   ),  
  165.                   new ParameterExpression[0]  
  166.                 )  
  167.               }  
  168.             ),  
  169.             Expression.Constant(  
  170.               0,  
  171.               typeof(int)  
  172.             )  
  173.           }  
  174.         ),  
  175.         new ParameterExpression[] { mParam, nParam }  
  176.       );  
  177.     expr.Compile()(1, 2);  
  178.   }  
  179. }  



敬请期待后续文章。 

posted @ 2014-03-31 16:15  脾气不坏  阅读(238)  评论(0)    收藏  举报