(转载)转 lambda表达式概念

2023-10-02 15 0

点击打开链接

前言

 

在LINQ刚发布的时候,一直也没有时间去研究下LINQ,特别是在当时各种LINQ to ***纷纷出现,看得眼花缭乱。

在LINQ中增加的lambda表达式一直觉得挺神秘的,看到各位高手把lambda表达式运用得如火纯清,很是羡慕. 最近抽

空学习了解了下LINQ, 对学习的过程进行记录。文章基础,高手请飘过 🙂

计划学习的主要内容是lambda表达式,LINQ to Objects, LINQ to XML.  三个部分。

学习之前,推荐一款帮助学习LINQ的优秀工具LINQPad,下载地址:LINQPad下载 。

【另注:学习过程未免出现差错,欢迎指正】

 

 

 

Lambda表达式的概念

 

什么是lambda表达式?Lambda 表达式是一种匿名函数,它可以包含表达式和语句,并且可用于创建委托或

表达式目录树类型。我们使用lambda表达式可以帮助我们编写精简和紧凑的代码,许多操作中允许自定义排序和过

滤的函数,在.NET2.0的时候通常使用委托函数来实现,在.NET3.5可以使用lambda表达式。

现在举例说明lambda表达式:  Func<int,int> addOne= item=> item+1 ,其中操作符 “=>”读作“Goes to”,

可以理解为操作符左边的是函数的参数,操作符右边是函数体内容。上面我们定义的lambda表达式等同于函数如下:

 

1  int  addOne( int  item)
2  {
3  return  item + 1 ;
4  }

 

那么什么样的表达式才是合法的lambda表达式呢?

1. lambda表达式可以是多个参数。 如:  (item1,item2)=>item1+item2;

2. lambda表达式可以是0个参数。 如: ()=>"csharp";

3. lambda表达式可以显示指定参数类型。 (int item1,string item2)=>item1+item2;

4. lambda表达式函数体可以使用多条语句. (item1)=>{string ret="hello"+item1;return ret;};

 

使用lambda表达式的时候,不得不提到泛型委托。在上面我们定义的表达式如:(item1,item2)=>item1+item2;

只是定义的表达式,我们如何调用呢?我们可以定义自己的函数委托来引用lambda表达式,如下

 

复制代码
1  public delegate int  addOneDelegate( int  item1, int  item2);
2    void  Main()
3  {
4  addOneDelegate fun = (item1,item2) => item1 + item2;
5  var result = fun( 123 , 456 );
6  result.Dump( " 结果 " );
7  }
8 
9 
10  .结果
11    579
复制代码

 

在这里我们可以使用.NET类库中已经提供的泛型委托Func<T>和Action<T>来引用lambda表达式.代码如下

 

复制代码
1  void  Main()
2  {
3  Func < int , int >  fun = (item1,item2) => item1 + item2;
4  var result = fun( 123 , 456 );
5  result.Dump( " 结果 " );
6  }
7 
8  .结果
9    579
复制代码

关于Func<T>是泛型委托,最后的一个类型是指返回结果的类型,前面都是输入参数类型,上面的例子中,我们

的输入类型是INT,返回类型也是INT。同样如果我们定义Func<int,string,bool>,是指输入参数有两个,一个是int

类型,一个是string类型,函数返回是bool类型。使用泛型委托可以帮助我们方便引用lambda表达式。Func<T>提供

了多个重载,如下

 

1  public delegate  T Func < T > ();
2    public delegate  T Func < A0, T > (A0 arg0);
3    public delegate  T Func < A0, A1, T > (A0 arg0, A1 arg1);
4    public delegate  T Func < A0, A1, A2, T > (A0 arg0, A1 arg1, A2 arg2);
5    public delegate  T Func < A0, A1, A2, A3, T > (A0 arg0, A1 arg1, A2 arg2, A3 arg3);
   

 

 

在这里,需要提到一些关于lambda表达式的特性和规则。

1. lambda表达式的引用变量必须是显式类型。编译器对lambda表达式的类型推断是通过返回的引用变量的类型指定。

如下面的语句是非法的。

 

1  void  Main()
2  {
3  var c = n => n + 1 ; // Error,Cannot assign lambda expression to an implicitly-typed local variable
4     Func < string >  cc => n + 1 ; // Ok
5    }

 

2. 在lambda表达式中可以直接访问本地变量和全局变量。

 

 

 

复制代码
1  public static string  grobalVar = " grobal string " ;
2    void  Main()
3  {
4  string  localVar = " local string " ;
5  Func < string , string >  fun =  n =>  n + "  can access  " + grobalVar + "  and  " + localVar;
6  fun( " lambda " ).Dump();
7  }
8 
9 
10  结果:
11  lambda can access grobal  string  and local  string
12   
复制代码

 

3. lambda表达式的参数可以是ref或out方式传入,在通过ref或out方式传入的时候必须指定参数的具体类型。

 

 

复制代码
1  public delegate int  RefParameterFunction( ref int  n);
2    void  Main()
3  {
4  int  x = 10 ;
5  RefParameterFunction fun =  ( ref int  n) =>  n ++ ;
6  fun( ref  x);
7  x.Dump();
8  }
9 
10 
11  结果
12    11
复制代码

 

 

4. lambda表达式的参数可以支持不定参数数传入。

 

 

复制代码
1  public delegate int  AddFunction( params int [] ints);
2    void  Main()
3  {
4  int [] x = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };
5  AddFunction fun =  (items) =>  
6 
7  int  count = 0 ;
8               foreach ( int  item  in  items) 
9  {count = count + item;} 
10              return  count;
11  };
12  fun(x).Dump( " 求和: " );
13  }
14 
15 
16  求和:
17    45
复制代码

 

 

 

 

 

Lambda表达式树的概念和示例

 

Lambda另一个强大的特性就是表达式树,lambda表达式都可以通过表达式树来描述,就不用在代码

中直接编写表达式。这样的优势就是表达式可以在运行的时候编译运行,而且可以对lambda表达式进行动态修改。

要使用lambda表达式树,首先提到一个表达式的泛型类Expression<T>,(域名空间System.Linq.Expressions),

这个类是保存表达式的结构信息。我们把Expession看作一棵树结构,每个结点都是由两部分组成,左树和右树,一直这样

递归下去。这里需要说明一下,刚开始使用表达式树的时候容易和表达式产生混淆,比如:

 

 

复制代码
1  void  Main()
2  {
3 
4  Expression < Func < int , int >>  tree  =  x => x + 1 ;
5  Func < int , int >  exp =  x =>  x + 1 ;
6 
7  tree( 1 ); // 'tree' is a 'variable' but is used like a 'method'
8     exp( 1 ); // 输出2
9    }
10 
11   
复制代码

 

注意:tree只是lambada表达式的树形结构信息,并不是函数可以直接调用。

 

 

现在我们对lambda表达式的树结构输出来查看下,举例:我们编写一个验证三角形是否直角三角形,通过沟谷定律,

我们很容易编写lambda表达式为:

 

1  (x,y,z) =>  (x * x + y * y) == z * z

 

现在我们使用LINQPad的Dump()函数进行输出显示:

 

1  void  Main()
2  {
3  Expression < Func < int , int , int , bool >>  tree  =  (x,y,z) => (x * x + y * y) == z * z;
4  tree.Dump();
5  }

输出结果如下:

 

 

通过输出的图形,我们可以清楚的看出整个lambda表达式是由LEFT和RIGHT两部分组成的,Left部分和right部分之间

的关系通过 NodeType属性指定,所有的NodeType类型通过枚举(System.Linq.Expressions.ExpressionType )定义,

而结点的Type可以看作返回类型,比如我们定义的  tree的Type是Func<int,int,int,bool>,而Type是Lambda。

 

那么如何把表达式树转换为可以直接使用的函数呢?Expression类提供了函数Compile(),就可以把我们定义的lambda

表达式树编译为实际的函数,代码如下:

 

复制代码
1  void  Main()
2  {
3  Expression < Func < int , int , int , bool >>  tree  =  (x,y,z) => (x * x + y * y) == z * z;
4  Func < int , int , int , bool >  fun =  tree.Compile();
5  fun( 3 , 4 , 5 ).Dump();
6  }
7     
9  结果
10  True
复制代码

 

 

我们了解到了lambda表达式树的基本概念,现在我们自行构造一个lambda表达式树。还是以上面的验证是否是直角

三角形为例,我们通过System.Linq.Expressions提供了表达式类来构造这个表达式,不参考LINQPad输出的结构。现在

我们分析表达式的树结构,(x,y,z) => (x*x + y*y)== z*z 按照操作符把表达式分为left tree和right tree。比如首先

我们把整个表达式分为左树:x*x + y*y,  右树:z*z, 关系:Equal,以此画出阿里如下:

我们已经把表达式树分析出来,现在我们开始使用.NET提供的表达式类来构造这棵表达式树,在这棵树比较简单,

我们比较用到的类包括二元表达式类(BinaryExpression)和参数表达式类(ParameterExpression)。现在我们

从树的叶结点开始构造。

 

首先我们需要制定表达式中参数和参数的类型。

 

1  ParameterExpression expX =  Expression.Parameter( typeof ( int ), " x " );
2  ParameterExpression expY =  Expression.Parameter( typeof ( int ), " y " );
3  ParameterExpression expZ =  Expression.Parameter( typeof ( int ),  " z " );
   

 

 

接着我们使用二元表达式将参数表达式关联起来,X和X,Y和Y,Z和Z,二元关系都是乘.

 

1  BinaryExpression mulX  =  Expression.Multiply(expA, expA);
2  BinaryExpression mulY =  Expression.Multiply(expY, expY);
3  BinaryExpression mulZ =  Expression.Multiply(expZ, expZ);
   

 

 

然后我们将X*X+Y*Y通过 加二元表达式关联起来.

 

1  BinaryExpression addXY  =  Expression.Add(mulX,mulY);

 

 

最后我们将X*X+Y*Y 和Z*Z通过 等于二元表达式关联起来.

 

BinaryExpression final =  Expression.Equal( mulZ, addXY );

 

 

现在我们构造完成后,可以通过编译来执行,下面是完整的代码:

 

复制代码
1  void  Main()
2  {
3  ParameterExpression expX =  Expression.Parameter( typeof ( int ), " x " );
4  ParameterExpression expY =  Expression.Parameter( typeof ( int ), " y " );
5  ParameterExpression expZ =  Expression.Parameter( typeof ( int ),  " z " );
6  BinaryExpression mulX =  Expression.Multiply(expX, expX);
7  BinaryExpression mulY =  Expression.Multiply(expY, expY);
8  BinaryExpression mulZ =  Expression.Multiply(expZ, expZ);
9  BinaryExpression addXY  =  Expression.Add(mulX,mulY);
10  BinaryExpression final =  Expression.Equal(mulZ, addXY);
11  Expression < Func < int int int bool >>  square  =  
                Expression.Lambda
< Func < int int int bool >> (final, expX, expY, expZ);
12  Func < int int int bool >  xx =  square.Compile();
13  xx( 3 , 4 , 5 ).Dump();
14  }
15 
16 
17  结果:
18  True
19   
复制代码

 

 

 

 

 

Lambda表达式的简单应用

 

1. 对数组的自定义排序。

 

复制代码
1  void  Main()
2  {
3  string [] items = { " csharp " , " cpp " , " python " , " perl " , " java " };
4  List < string >  list = items.ToList();
5  list.Sort((x,y) => y.Length - x.Length);
6 
7  list.Dump();
8  }
9 
10  结果:
11  python
12  csharp
13  perl
14  java
15  cpp        
复制代码

 

2. 对数组数据进行搜索

 

复制代码
1  void  Main()
2  {
3  string [] items = { " csharp " , " cpp " , " python " , " perl " , " java " };
4  List < string >  list = items.ToList();
5  var result =  list.FindAll(x =>  x.Length == 4 );
6 
7  result.Dump();
8  }
9 
10  结果:
11  perl
12  java
复制代码

 

 

3. 对数组数据进行直接更新

 

复制代码
1  void  Main()
2  {
3  string [] items = { " csharp " , " cpp " , " python " , " perl " , " java " };
4  var result =  items.Select(n =>  n + "  :  " + n.Length);
5  result.Dump();
6  }
7 
8  结果:
9  csharp :  6
10  cpp :  3
11  python :  6
12  perl :  4
13  java :  4
       
复制代码

 

 

 

 

 

总结

 

这篇学习记录对Lambda表达式和Lambda表达式树的最基础进行描述,对lambda没有更多深入的研究。比如在表达式树

的的动态修改,更多复杂的lambda表达式,lamdba表达式树对更复杂函数的构造。如果有兴趣,可以继续研究。

另:这是我发布的第一篇随笔,写一篇随笔原来也是挺艰难,水平有限,希望大家指正,谢谢。

代码编程
赞赏

相关文章

ArcGIS制图表达—突出实体目标
ArcGIS制图表达—创建天桥
ArcGIS比较好玩的功能:Geochat
Feature Service(一)
ArcGIS10拓扑规则-线规则
ArcGIS10拓扑规则-面规则