LINQ学习之旅——准备(2)

  上一节,我有提到过LINQ中还大量使用了C#3.0的一些新的语言特性,比如:局部变量类型推断、Lambda表达式、表达式树以及扩展方法。这些特性就是我今天主要讲的内容,也是学习LINQ的第二项准备工作。

  1.局部变量类型推断是指编译器可以自动推断局部变量的数据的类型。开发者在定义局部变量时,不需要强制定义数据类型,而是让编译器通过相关上下文来自动推断数据类型。C#3.0中是通过var关键字来实现局部推断功能。 请看下述示例:

1  var x =5;
2 var y =5.5;

单步调试结果

正如结果所示,编译器会自动推断变量x为int类型,变量y为double类型。但要注意的是,局部变量推断顾名思义就是只能针对局部变量,而不能针对成员变量和形式参数(函数中形参)。另外大量使用局部类型推断也会降低代码的阅读性,加剧代码的维护难度

  2.在上节中说到过C#2.0里的匿名方法是C#3.0的Lambda表达式的基础,匿名方法动给已存在的方法注入代码,编译器会在编译匿名方法时自动生成相关辅助的委托对象。而Lambda表达式是进一步简化匿名方法的语法。

(1)定义委托

View Code
1 //定义泛型委托
2 delegate T delegateMul<T>(T x, T y);

(2)定义以委托为参数类型的函数

View Code
 1 ///<summary>
2 /// 累乘
3 ///</summary>
4 ///<typeparam name="T">泛型</typeparam>
5 ///<param name="nums">数据集合</param>
6 ///<param name="func">乘法委托</param>
7 ///<returns>累乘结果</returns>
8 static T Multi_Mul<T>(T[] nums, delegateMul<T> func)
9 {
10 //default自动识别T为数值类型还是引用类型
11 //如果是数值类型则初始化为0,如果是引用类型则为null
12 T result =default(T);
13
14 //是否为第一次循环
15 bool firstLoop=true;
16
17 foreach (T value in nums)
18 {
19 if(firstLoop)
20 {
21 result=value;
22 firstLoop=false;
23 }
24
25 //执行乘法委托
26 result = func(result, value);
27 }
28 return result;
29 }

(3)使用Lambda表达式

 1        staticvoid Main(string[] args)
2 {
3 int[] nums =newint[5] { 1, 2, 3, 4, 5 };
4 int pro;
5
6 pro = Multi_Mul<int>(nums,
7 (int a, int b) =>//Lamdba表达式
8 {
9 return a * b;
10 }
11 );
12 Console.WriteLine("乘积:"+pro);
13
14 Console.Read();
15 }

(4)结果

因为泛型可以自动根据上下文推断数据类型,所以在调用方法Multi_Mul和执行乘法委托时,可以自动推断出变量a和b为int类型。另外如果Lambda表达式的主体代码({}中内容)只包含一个return语句,那么花括号{}和return关键字可省略。经以上所述,对Lambda表达式可修改如下:

1  pro = Multi_Mul(nums,
2 (a, b) => a * b //Lamdba表达式
3 );

以上述示例中的Lambda式为例,举出下面几个Lambda表达式常用的几种形式:

1 (int a,int b)=>{return a*b;}
2 (int a,int b)=>a*b;
3 (a,b)=>return a*b;
4 (a,b)=>a*b;
5 (x)=>sum+=x;
6 x=>sum+=x;//当只有一个参数时,括号可以省略
7 ()=>sum+5;//当没有参数时,括号不可以省略

  3.表达式树(exprssion tree)是Lambda表达式以树的形式表现出来的另外一种数据结构。一个表达式树是一个System.Linq.Expressions.Expression<T>泛型的实例,委托类型T是与此表达式树想对应的Lambda表达式的委托类型注意并不是所有的Lambda表达式都可以转化成表达式树,只有那些包含的表达式主体内容的Lambda表达式才可以转换,如果只是声明性质的语句,是不能转换的。表达式树示例:

(1)定义委托

View Code
//定义泛型委托
delegate T delegateMul<T>(T x, T y);

(2)委托调用和表达式树形式调用Lambda表达式

 1 staticvoid Main(string[] args)
2 {
3 //委托形式
4 delegateMul<int> lambda_f = (a, b) => a * b;
5 //表达式树形式
6 Expression<delegateMul<int>> expreesion_f = (a, b) => a * b;
7
8 Console.WriteLine("委托调用:");
9 Console.WriteLine("乘积:"+lambda_f(5,10));
10
11 Console.WriteLine("表达式树调用:");
12 Console.WriteLine("乘积:"+ expreesion_f.Compile()(10,15));//使用表达式树需要调用Compile方法
13
14 Console.Read();
15 }

(3)结果

在上述代码中之所以要用到Compile()方法,是因为表达式树本身不是一个委托,通过Compile()方法可以其转化成表示Lambda表达式的委托。表达式树的优势在于它可以在系统运行时动态编译一段可执行代码,正因为是动态编译,系统可以在执行时把一组相关的表达式优化组合一起编译,提高了查询的效率。对于下面的一个示例:

1 int[] nums =newint[5] { 1, 2, 3, 4, 5 };
2 var num = nums.Where(i => i >2).OrderByDescending(i => i);

其中Where和OrderByDescending方法使用Lambda表达式作为参数,当这个代码执行时,先调用Where方法,在调用OrderByDescending方法,这种线性的执行顺序不会对本例中数据源(数组nums)带来效率上的问题,但如果对于容量更大的数据库而言的话,就必须考虑执行的效率,如果根据本例的执行顺序,会使查询效率降低。如果能设法同时把Where和OrderByDescending一次性发给数据库执行,就会大大提高执行效率。表达式树就可以实现此功能,在系统动态编译时,将一组相关的表达式树进行优化组合,编译成一条SQL语句,发给数据库。

  4.扩展方法是定义在静态类内部的静态方法,开发人员可以像用实例方法那样来使用扩展方法。根据C#的语法规定,实例方法只能通过建立一个实例对象才能调用,不能通过类名来调用,相反的,静态方法只能通过类名来调用。而扩展方法提供了一个新的机制可以在对象实例上调用静态方法。扩展方法主要用于在不改变现有的类型代码下,扩展现有类型的功能。所扩展的功能只限于当前上下文有效,并不会对原有类型的定义的代码产生变化。它的一般声明格式如下:

  public static class 类名

  {

    public static 返回类型 扩展方法名 (this 要扩展的类型 参数名)

    {

      函数体;

    }

  }

通过示例说明:

(1)自定义复数类

View Code
 1 //复数类
2 publicclass Complex
3 {
4 //实数
5 protecteddouble real;
6 publicdouble Real { get { return real; } }
7
8 //虚数
9 privatedouble imag;
10 publicdouble Imag { get { return imag; } }
11
12 public Complex(double real=0, double imag =0)
13 {
14 this.real = real;
15 this.imag = imag;
16 }
17 }

(2)定义扩展方法类

 1 //扩展方法类
2 publicstaticclass Extensions
3 {
4 ///<summary>
5 /// 把复数类型转换为double类型(Complex的扩展方法)
6 ///</summary>
7 ///<param name="com">复数</param>
8 ///<returns>双精度值</returns>
9 publicstaticdouble ToDouble(this Complex com)
10 {
11 return com.Real;
12 }
13
14 ///<summary>
15 /// 把复数转换为字符串类型(Complex的扩展方法)
16 ///</summary>
17 ///<param name="com">复数</param>
18 ///<returns>字符串值</returns>
19 publicstaticstring ToValue(this Complex com)
20 {
21 string str=com.Real.ToString();
22 if (com.Imag >0)
23 str+="+";
24 if (com.Imag!=0)
25 str+=com.Imag+"i";
26 return str;
27 }
28 }

(3)调用扩展方法

1 staticvoid Main(string[] args)
2 {
3 Complex com =new Complex(10, -10);
4 var real = com.ToDouble();//调用扩展方法
5 Console.WriteLine("复数"+com.ToValue()+"转换为double类型:"+real);
6
7 Console.Read();
8 }

(4)结果

扩展方法可以包含多个参数,但是只有第一个参数可以被this关键字所修饰,用来代表要被扩展的类型。扩展方法不等同于扩展类的内部方法,其对被扩展类型的内部访问有很大局限性。就像上述定义的扩展方法ToDouble()中,想要访问其中的Real属性,不可以直接调用Real,需要通过参数com来访问属性成员Real,且只能访问Public限制级的成员

posted @ 2011-08-28 13:18  Rookie_J  阅读(2130)  评论(1编辑  收藏  举报