其实LINQ在语法上很多都是运用了C#语言的一些特性,C#语言从2.0升级到3.0,并没有升级C#2.0的运行平台CLR,只是在原有语法基础上添加了新的特性,这些特性依赖于C#3.0的编译器。使用C#3.0的编译器编译过的代码可以在支持C#2.0的运行平台CLR上运行,即完全可以在.NET2.0平台上运行编译过的.NET3.0代码。C#2.0中的泛型,匿名方法,以及C#3.0中的Lambda表达式、表达式树以及扩展方法都是LINQ的基础。这一节我将介绍有关C#2.0中的泛型、委托、匿名方法、yield关键字以及IEnumerable接口的知识,这些和LINQ的实现和使用有着密切的关系。是我们学习之旅的第一项准备工作。
1.目前大部分语言都是强类型语言,这些语言要求程序中的变量或对象要有明确的类型,在不同类型之间的转化需要遵守一定的规则。强类型的编程有助于代码的安全性,但因为限制多了,就会显得不够灵活。如下代码所示:
1 int Max(int x,int y)2 {3 if(a<b)4 return b;5 else6 return a;7 }
比较两个整数的大小。但是这两个参数都是强类型int,如果我要比较两个双精度、浮点数类型或两者都有的数据,那就需要在定义几个新的方法了,可是这几个方法最终的方法体确实一样的,这样就使得代码有了冗余性。也许有读者会问,那么让参数的类型为object类型就可以了,因为它是所有类型的基类型。但是问题在于两个object类型的参数是无法通过比较运算符来对比的,而需要借助IComparable接口来实现比较运算的:
1 IComparable Max(IComparable x, IComparable y)2 {3 if (x.CompareTo(y) >0)4 return x;5 else6 return y;7 }
但使用这段代码需要进行类型间的转换:
1 int x =10, y =5,z;2 z =(int)Max(x, y);
频繁的类型转换会消耗计算机资源,导致程序运行效率下降,为了解决上述问题C#2.0提出泛型的概念,使用泛型可以将类型转化工作推迟到程序执行时由Jitter(即.net运行时的一部分,负责将IL代码转换成机器代码,而C#编译器将源代码转换成IL代码)执行,经过Jitter编译过的机器代码就不再有类型转换的内容了:
T Max<T>(T x, T y) where T:IComparable<T> { if (x.CompareTo(y) >0) return x; else return y;}
执行该函数:
1 int x =10, y =5, z;2 z =Max<int>(x, y);//或Max(x,y)
上述代码中的调用方法Max<int>(x,y)和Max(x,y)都可以,后者编译器会根据x和y的实际类型来推断出T的具体类型。泛型除了可以定义方法,还可以定义class或interface。泛型要引用空间System.Collections.Generic;
2.detegate委托是一个类,类内部封装了一个或多个方法,可以通过委托来调用其封装的方法。委托是匿名方法的基础。且委托的定义需要使用一个完整方法的签名,就像是C++中的函数指针。
(1)定义一个委托:
1 //定义返回类型和参数类型都为double的委托2 publicdelegatedouble addDeletegate(double a, double b);
(2)符合委托addDeletegate定义的方法签名的具体方法:
1 publicstaticdouble add(double a, double b)2 { 3 return a + b;4 }
(3)以委托类型作为参数来调用具体方法:
1 publicstaticvoid Repeat10Add(addDeletegate add)2 {3 double start =0;4 for (int i =1; i <10; i++)5 {6 start = add(start, i);7 Console.WriteLine(start);8 }9 }
(4)调用此方法:
1 staticvoid Main(string[] args)2 {3 Repeat10Add(add);4 }
(5)结果:
其实委托主要的作用就是先向已经存在的方法中动态嵌入一段程序代码。
3.匿名方法顾名思义就是没有名字的方法,我们对上面的委托示例稍作更改,就可以定义匿名方法(它是Lambda表达式的基础)。
1 Repeat10Add2 (3 //使用委托定义匿名方法4 delegate(double f,double g)5 {6 return f+g;7 }8 );
结果和上述相同。
4.C#1.0中有两个这样的接口:
publicinterface IEnumerator{ //获取集合中的当前元素object Current { get; } //将枚举数推进到集合的下一个元素bool MoveNext(); // 将枚举数设置为其初始位置,该位置位于集合中第一个元素之前void Reset();}publicinterface IEnumerable{ // 返回一个循环访问集合的枚举器 IEnumerator GetEnumerator();}
一个实现了IEnumerator接口的对象本身就可列举,而实现接口IEnumerable的对象则可以通过GetEnumerator()返回的IEnumerator来实现列举功能。在C#中有个大家常用的关键字foreach,它就是用来对IEnumerable对象实现列举,在用foreach列举时,每次循环会自动调用GetEnumerator()和其返回的对象IEnumerator的MoveNext方法。
5.yield关键字是用来帮助IEnumerable对象自动生成一个辅助IEnumerator对象。且yield关键字只能用在retur关键字或break关键字之前。最后综合IEnumerator、IEnumerable以及关键字yield三个知识点,用一个示例来说明它们三者之间的联系:
(1)定义一个实现了接口IEnumerable的类MyYield:
1 class MyYield : IEnumerable 2 { 3 publicint StartCountdown; 4 5 public IEnumerator GetEnumerator() 6 { 7 for (int i = StartCountdown; i >=0; i--) 8 yieldreturn i;//yield自动生成一个实现了接口IEnumerator的类 9 }10 }
(2)建立Myield类的对象,且通过foreach实现列举功能:
MyYield myYield =new MyYield() { StartCountdown=10}; foreach (int n in myYield) { Console.WriteLine(n); }
(3)结果:
这端程序的执行过程:在执行foreach遍历时,执行类MyYield中的成员函数GetEnumerator()返回一个自动生成且继承了接口IEnumerator的类,该类的Current等于10,当开始下一轮遍历时,先执行之前所返回的类的MoveNext()方法,若返回true,继续执行,依次重复以上的过程。这个执行的过程也可以用以下代码来代替说明:
1 MyYield myYield =new MyYield() { StartCountdown=10};2 3 IEnumerator i = myYield.GetEnumerator();4 5 while (i.MoveNext())6 {7 int n = (int)i.Current;8 Console.WriteLine(n);9 }
结果和上个的示例一样。
6.在示例的编写及运行时,我发现了几个问题,自己也尝试去理解和实现,但最后还是没有成功,希望得到园里的大虾的帮助,以下是列出几个问题:
(1)在使用泛型时,对于上述同一个示例,如果我要实现一个浮点数和一个双精度数据的大小比较,返回一个双精度类型的较大值,那么我是这样尝试的改编:
1 publicstatic T2 Max<T1, T2>(T1 x, T2 y)2 where T1 : IComparable<T1>3 where T2 : IComparable<T2>4 {5 if (x.CompareTo(y) >0)6 return x; 7 else8 return y;9 }
但是编译时x.Compare(y)处出现无法从T2转化为T1,以及return x中无法将类型T1隐式转化为T2无法将类型T1隐式转化为T2的错误,遇到这个错误后,我就马上想到用显式转换改为x.Compare((T1)y)和return (T2)x,但结果还是编译出错:无法从类型T2转化为T1和无法从类型T1转化为T2;所以不知该怎么样修改才能完成上述功能;
(2)对于最后一个示例执行过程的说明,是我参考了一些网上资料,以及根据运行过程自己总结的,但不敢确定其原理是否正确,也希望有哪位大虾能够详细讲解。
来源:https://www.cnblogs.com/CaiNiaoZJ/archive/2011/08/26/2154758.html