[.net 面向对象编程基础 ] ( 19) LINQ 基础
上两节我们介绍了 .net 的数组、集合和泛型。我们说到,数组是从以前编程语言延伸过来的一种引用类型,采用事先定义长度分配存储区域的方式。而集合是 .Net 版本初期的用于解决数据集检索方便而设计的,它比数组的优势除了检索方便之外,还可以在使用过程中自动分配存储区域,不需要事先定义大小。但是集合存在类型不安全以及频繁装箱、拆箱操作带来的性能问题。泛型是 .net 2.0 以后为了解决集合的缺陷而设计的,采用实例调用阶段再声明类型的方法,即解决了安全问题,也解决了效率问题。
随着 .net 3.5 以后版本的发展 , 微软设计了 LINQ, 让我们检索更加方便,开发更加高效了。
1.LINQ 概念
LINQ ,语言集成查询( Language Integrated Query ) 是一组用于 c# 和 Visual Basic 语言的扩展。 它允许编写 C# 或者 Visual Basic 代码以查询数据库相同的方式操作内存数据。
以上是来自百度百科的定义。
通过英文名描述,我们可以看出, 它就是一种让开发者像查询数据库一样,去检索内存数据的方案。
2.LINQ 学习之前需要掌握以下知识点
看着有点多,但是都是学好 LINQ 的基础,我们说了 LINQ 就是像 SQL 语句一样操作内存数据,那么学习 SQL 要掌握表,字段,视图的基础概念, LINQ 也一样。
2.1 隐式类型
之前我们定义变量时,需要指定一个类型, foreach 遍历时,也要指定类型。隐式类型的出现我们不再需要做这个工作了,而使用 var 定义就可以了,这点更像弱类型语言比如 javascipt 。但是 .NET 并不是弱类型语言,隐式类型的定义写法如下:
var i=1; var b=”xyz”; var obj=new MyObj();
以上隐式类型等效于
int i=1; string b=”xyz”; MyObj obj=new MyObj();
A. 有了隐式类型,可以编码中使用 var 定义任意类型变量
B. 隐式类型并不会影响程序性能, .NET 会在编译时帮我们转换成具体的数据类型,因此我们必须声明隐式类型时赋值,不然 .NET 不能判断具体转成什么类型而报错。
C. 隐式类型 var 使我们开发节约了不少时间。定义一个变量时,类型需要输两次, var 一下就搞定了。在 foreach 遍历的时候也可 以使用 var 来书写循环变量的类型。
2.2 匿名类型
我们 new 一个对象,它里面的元素,类型可以不申明,而使用匿名方式。如下:
//匿名类型 var obj =new { str = "abc", Guid.Empty,myArray = new int[] { 1, 2, 3 } }; Console.WriteLine(obj.str); Console.WriteLine(obj.Empty); Console.WriteLine(obj.myArray[0]); Console.ReadLine();
运行结果如下:
我们看到 new 一个对象后 , 自动为对象定义了属性 , 并为这些属性赋值 , 当把一个对象的属性拷贝到匿名对象中时,可以不用显示的指定属性的名字,这时原始属性的名字会被“拷贝”到匿名对象中 . 如下图:
自动创建了 obj.Empty 属性名。 类型的。
对象时很有用。
2.3 自动属性
记得在“重构”一节中,说了属性的重构,只需要字段名上使用 vs.net 提供的“封装字段”,就为我们生成了相对应的属性 , 如下图:
(图1)
(图2)
(图3)
自 .net 3.0 以后,我们代码可以写的更简单了,代码如下:
//自动属性 class Flower { public string Leaf{get;set;} public string Name{get;set;} }
重构当然只是 .net 的辅助功能,对于自动属性小伙伴们肯定会担心这样写的效率,这点完全可以放心,跟 var 隐式类型一样 ,.net 在编译的时候,会帮我们写完整的,没有任何性能问题。
2.4 初始化器
对于一个对象的初始化,假如有以下两个对象 :
//初始化器 class Flower { public string Leaf{get;set;} public string Name{get;set;} } class Bear { public Bear(string name){} public string Name { get; set; } public double Weight { get; set; } }
在 3.0 以前的版中,我们一般类似于如下写法:
//初始化器 Flower flower = new Flower(); flower.Leaf = "叶子"; flower.Name = "棉花";
在 3.0 以后的版本中,我们可以这样写 :
//.net 3.0以后对象初始化可以如下写法 Flower flower2 = new Flower() { Name = "桃花", Leaf = "桃叶" }; //构造函数带参数对象初始化 Bear bear = new Bear("北极熊"){ Name="熊熊", Weight=300}; //集合初始化 var array = new List<char>() { 'a', 'b','c', 'd', 'e','f' };
我们可以看出,在写法上简洁好多了。特别是对泛型集合的赋值,相当简洁明了。
2.5 匿名方法
说到匿名方法,就是说在委托一个方法的时候,可以把方法写在委托声明的时候,简单说就是不用把方法单独写出来。在说这个之前,我们本来是要介绍一下委托的,但是下一节,我们会重点说明委托,在这里只需要了解匿名方法就可以了,下面看一个例子:
1 // 委托函数 2 Func<int, int, string> func1 = Adds; 3 // 匿名方法 4 Func<int, int, string> func2 = 5 delegate(int a, int b) 6 { 7 return a+"+"+b+"等于几?" + Environment.NewLine +(a+b).ToString(); 8 }; 9 // Lambda表达式 10 Func<int, int, string> func3 = 11 (a, b) => { return a + "+" + b + "等于几?" + Environment.NewLine + (a + b).ToString(); }; 12 13 // 调用Func委托 14 string sum = func2(45, 36); 15 16 Console.WriteLine(sum); 17 Console.ReadLine();
//委托方法 static string Adds(int a, int b) { return a + "+" + b + "等于几?" + Environment.NewLine + (a + b).ToString(); }
通过使用匿名方法,可以访问上下文中的变量,在方法本身不是很长的情况下,轻量级的写法,非常实用。
这点在下一节委托中具体说明,小伙伴在这里没看明白也没关系。
2.6 扩展方法
1) 扩展方法声明在静态类中,定义为一个静态方法,其第一个参数需要使用 this 关键字标识,指示它所扩展的类型。
2) 扩展方法可以将方法写入最初没有提供该方法的类中。还可以把方法添加到实现某个接口的任何类中,这样多个类就可以使用相同的实现代码。( LINQ 中, System.Linq.Queryable.cs 和 System.Linq.Enumerable.cs 正是对接口添加扩展方法)
3) 扩展方法虽定义为一个静态方法,但其调用时不必提供定义静态方法的类名,只需引入对应的命名空间,访问方式同实例方法。
4) 扩展方法不能访问它所扩展的类型的私有成员。
例子:
public static IEnumerable<TSource> MyWhere<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate) { foreach (TSource item in source) { if (predicate(item)) yield return item; } }
我们再看一下扩展方法的厉害的地方,要给一个类型增加行为,不一定要使用继承的方法实现,还可以这样写:
var a = "aaa"; a.PrintString(); Console.ReadKey();
但通过我们上面的代码,就给 string 类型 " 扩展 " 了一个 PrintString 方法。
( 1 )先决条件
<1> 扩展方法必须在一个非嵌套、非泛型的静态类中定义
<2> 扩展方法必须是一个静态方法
<3> 扩展方法至少要有一个参数
<4> 第一个参数必须附加 this 关键字作为前缀
<5> 第一个参数不能有其他修饰符(比如 ref 或者 out )
<6> 第一个参数不能是指针类型
( 2 )注意事项
<1> 跟前面提到的几个特性一样,扩展方法只会增加编译器的工作,不会影响性能(用继承的方式为一个类型增加特性反而会影响性能)
<2> 如果原来的类中有一个方法,跟你的扩展方法一样(至少用起来是一样),那么你的扩展方法奖不会被调用,编译器也不会提示你
<3> 扩展方法太强大了,会影响架构、模式、可读性等等等等 ....
2.7 迭代器
( 1 )使用
我们每次针对集合类型编写 foreach 代码块,都是在使用迭代器
这些集合类型都实现了 IEnumerable 接口
都有一个 GetEnumerator 方法
但对于数组类型就不是这样
编译器把针对数组类型的 foreach 代码块
替换成了 for 代码块。
来看看 List 的类型签名:
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
IEnumerable 接口,只定义了一个方法就是:
IEnumerator<T> GetEnumerator();
( 2 )迭代器的优点:
假设我们需要遍历一个庞大的集合
只要集合中的某一个元素满足条件
就完成了任务
你认为需要把这个庞大的集合全部加载到内存中来吗?
当然不用( C#3.0 之后就不用了)!
来看看这段代码:
static IEnumerable<int> GetIterator() { Console.WriteLine("迭代器返回了1"); yield return 1; Console.WriteLine("迭代器返回了2"); yield return 2; Console.WriteLine("迭代器返回了3"); yield return 3; }
foreach (var i in GetIterator()) { if (i == 2) { break; } Console.WriteLine(i); } Console.ReadKey();
输出结果为:
迭代器返回了1 1 迭代器返回了2
大家可以看到:
当迭代器返回 2 之后, foreach 就退出了
并没有输出 “ 迭代器返回了 3”
也就是说下面的工作没有做。
( 3 ) yield 关键字
MSDN 中的解释如下:
在迭代器块中用于向枚举数对象提供值或发出迭代结束信号。
也就是说,我们可以在生成迭代器的时候,来确定什么时候终结迭代逻辑
上面的代码可以改成如下形式:
static IEnumerable<int> GetIterator() { Console.WriteLine("迭代器返回了1"); yield return 1; Console.WriteLine("迭代器返回了2"); yield break; Console.WriteLine("迭代器返回了3"); yield return 3; }
(4) 注意事项
<1> 做 foreach 循环时多考虑线程安全性
在 foreach 时不要试图对被遍历的集合进行 remove 和 add 等操作
任何集合,即使被标记为线程安全的,在 foreach 的时候,增加项和移除项的操作都会导致异常
<2>IEnumerable 接口是 LINQ 特性的核心接口
只有实现了 IEnumerable 接口的集合
才能执行相关的 LINQ 操作,比如 select,where 等
关于 LINQ 的具体操作,下一节继承。
2.8 Lambda 表达式
Lambda 表达式只是用更简单的方式来书写匿名方法 ,从而彻底简化 .NET 委托类型的使用。
Lambda 表达式在 C# 中的写法是“ arg-list => expr-body ”,“ => ”符号左边为表达式的参数列表,右边则是表达式体( body )。参数列表可以包含 0 到多个参数,参数之间使用逗号分割。
通过上面匿名方法的例子,我们可以看到,下面两段代码是等效的:
// 匿名方法 Func<int, int, string> func2 = delegate(int a, int b) { return a+"+"+b+"等于几?" + Environment.NewLine +(a+b).ToString(); }; // Lambda表达式 Func<int, int, string> func3 = (a, b) => { return a + "+" + b + "等于几?" + Environment.NewLine + (a + b).ToString(); };
Lambda 表达式基于数学中的 λ ( 希腊第 11 个字母 ) 演算 得名,而“ Lambda 表达式” (lambda expression) 是指用一种简单的方法书写匿名方法。
A.LINQ,语言集成查询( Language Integrated Query )是一种语言扩展,让我们像查询数据库一样去操作内存数据 ( 集合等 ).
B.匿名类型:使用 new 声明匿名类型时 , 不需指明类型, .NET 3.0 以后版本,可以自动完成类型指定和指定属性名称
C.自动属性: .net 3.0 以后版本中,我们定义属性,只需要简单书写 get;set; 就可以了, .net 在编译阶段会帮助我们完成书写,不会存在性能问题。
D. 初始化器 : 在 3.0 以后版本中,可以更加简单的书写对象初始化, 特别是对泛型集合的赋值,相当简洁明了。
E. 匿名方法: 在委托方法时,无需写明方法名称 . 使用匿名方法 可以访问上下文中的变量 , 在方法代码较少的情况下,轻量级实现委托,非常实用。
F. 扩展方法: 可以在不使用继承的方式给一个类型增加行为
G. 迭代器: 在遍历一个庞大集合时,不需要将它全部加载于是内存中,只要满足条件就可以返回了。
H.Lambda 表达式: Lambda 表达式基于数学中的 λ ( 希腊第 11 个字母 ) 演算 得名, 而“ Lambda 表达式” (lambda expression) 是指用一种简单的方法书写匿名方法 。
备注:本文参考了博友们的一些文章,数目较多,不一一列举,在此表示感谢。
对于 LINQ 的使用我们下一节详细说明。
==============================================================================================
返回目录
<如果对你有帮助,记得点一下推荐哦,有不明白的地方或写的不对的地方,请多交流>==============================================================================================