C# 3.0 (.NET 3.5, VS2008)
第三代C#在语法元素基本完备的基础上提供了全新的开发工具和集合数据查询方式,极大的方便了开发。
1. WPF,WCF,WF
这3个工程类型奠定了新一代.NET开发的客户端模型,通信模型,工作流模型。
WPF即将取代Winform成为新一代的桌面程序开发工具,控件与代码的超低耦合给开发带来了极大的方便,脱胎于MVC的MVVM模式也展现了极强的生命力。WCF即将融合原来的Web Service的各种提供方式,彻底屏蔽掉各种细节,开启Service服务的篇章。WF似乎使用的比较少,目前不做评论。
这3个概念每个都可以写上几本书,这里就不出丑了。
2. Lambda表达式
Lambda 表达式是一种可用于创建委托或表达式树类型的匿名函数。 通过使用 lambda 表达式,可以创建可作为参数传递或作为函数调用值返回的本地函数。 Lambda 表达式主战场是 Linq 查询表达式,这个在下面会提及;由于Lambda表达式是创建委托和表达式树的,所以它在除Linq之外的地方使用也很广泛,使用Lambda表达式可以写出相当优雅的委托代码。
若要创建 Lambda 表达式,需要在 Lambda 运算符 => 左侧指定输入参数(如果有),然后在另一侧输入表达式或语句块。 例如,lambda 表达式 x => x * x 指定名为 x 的参数并返回 x 的平方值。
左侧的参数列表可以带参数类型,也可以不带。指定参数类型时通常是因为右侧的语句块中使用了该类型的相关方法;当不带参数类型的时候,就由编译器根据上下文去推断,推断成立则没问题,推断不成立则编译错误。左侧的参数列表需要带"()",但是当只有一个无类型的参数时可省略。
右侧的如果是单个表达式的话可以不带"{}"和";",是语句块的话则要加上。
你可以将此表达式分配给委托类型:
delegate int del(int i); static void Main(string[] args) { del myDelegate = x => x * x; int j = myDelegate(5); //j = 25 }
当然也可以用于创建表达式树类型:
using System.Linq.Expressions; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Expression<del> myET = x => x * x; } } }
表达式树是一种动态创建表达式的工具,表达式树可以编译和运行;使用它可以动态的创建表达式,它的使用场合主要是在LINQ中区创建动态的查询条件表达式。关于表达式树,有兴趣的同学可以参考MSDN:http://msdn.microsoft.com/zh-cn/library/bb397951.aspx 。
总的来说,Lambda表达式是基于delegate又高于delegate的东东,青出于蓝而胜于蓝。它更加弱化的约束极大了丰富了表达式的含义,所谓描述越抽象,意义越丰富嘛!难怪抽象派艺术家的作品生命力都很强。在C#中,约束最强的应该就是继承(包括类的继承,接口的实现),它要求子类扩展父类或接口的时候方法时签名要完全匹配,包括返回值类型,函数名,参数类型等;约束次之的是delegate,它不再要求方法名相同了,只要求返回值类型与参数类型相同;到了lambda表达式了,约束更加弱化,连参数与返回值的类型都不要求完全匹配了,难怪有人说如果当初现有lambda表达式的话,就不会有匿名函数的语法了。
3. 对象初始化器和集合初始化器
直接初始化的时候就可以初始化是很方便的事,这个终于有了,虽说是在LINQ中使用最多,但是在其它场合使用对象初始化器和集合初始化器编程还是显得特别优雅。这个比较简单不多说了,看例子:
// 基本用法: User user =new User { Id = 1, Name ="AA", Age = 22 }; //嵌套使用: User user =new User { Id = 1, Name ="AA", Age = 22, Address = new Address { City ="NanJing", Zip = 21000 } }; //类似于对象初始化器初始化一个对象,集合初始化器初始化一个集合, //一句话,有了它你就不用在将元素通过Add逐个添加了: //基本使用: List<int> num =new List<int> { 0, 1, 2, 6, 7, 8, 9 }; //结合对象初始化器,我们可以写出如下简洁的代码: List<User> user =new List<User>{ new User{Id=1,Name="AA",Age=22}, new User{Id=2,Name="BB",Age=25}, };
4. 匿名类型
很多时候,我们需要使用一个临时的对象,按通常的做法,我们要先定义一个类吧,这样才能实例化这个类得到对象,这样实在是太累了;而且往往这样的对象只需要使用在局部的场合,使用以后就不再使用了,例如从数据库中查询出来的临时结果。在3.0中,我们终于不再需要预先定义一个类型了,CLR会提供一种形式让你动态的生成一个无类型的对象。
匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。 类型名由编译器生成,并且不能在源代码级使用。 每个属性的类型由编译器推断。
在看具体的例子之前,先看一个新的关键字:var。这个关键字在Javascript中使用广泛,在C#中使用var声明一个局部变量(只能在函数中使用var定义变量,这种变量官方称之为隐式类型局部变量)时,编译器会自动根据其赋值语句推断这个局部变量的类型。赋值以后,这个变量的类型也就确定而不可以再进行更改。但是这个var关键字最主要的用途是去生成匿名类型,比如表示一个Linq查询的结果。这个结果可能是ObjectQuery<T>或IQueryable<T>类型的对象,也可能是一个简单的实体类型的对象。这时使用var声明这个对象可以节省很多代码书写上的时间。
看一下匿名类型的使用方式:
var people = new { Name="AA", Age = 10 }; Console.WriteLine(people.Name);
需要注意的是,上面创建的people的Name和Age是只读的,不能去修改。
5. 扩展方法
Linq扩展了原来的IEnumerable得到IQueryable,如何自然的融入原来的集合中却是一个问题,有了这个语法糖,只要符合规定的语法,引入定义扩展方法的namespace,新添加的方法就像是对象原来就有的方法那样方便使用,这样就在不破坏原对象封装性的前提下给该对象添加了新的行为,还是蛮符合面向对象Open-Close原则的。
看网上的一个例子:
using System; using System.Text.RegularExpressions; namespace ConsoleApplication3 { class Program { static void Main(string[] args) { string email = "someone@somewhere.com"; Console.WriteLine(email.IsValidEmailAddress()); } } public static class Extensions { public static bool IsValidEmailAddress(this string s) { Regex regex = new Regex(@"^[w-.]+@([w-]+.)+[w-]{2,4}$"); return regex.IsMatch(s); } } }
如上代码所示,扩展方法为一静态方法,声明于一个静态类,其参数前加上一个this关键字,参数的类型表示这个扩展方法要对这个类型进行扩展。如上述代码表示其要对字符串类型进行扩展。使用起来也很方便,所有的string类型现在都多了一个叫IsValidEmailAddress的方法。
6. Linq
当所有的配角都到齐的时候,主角之一的Linq (Language Integrated Query,语言集成查询)应该要亮相了,它是3.0时代最为强悍的工具,从此查询集合数据有了最为便捷的方式,3.0中上面的许多特性基本上都是为了Linq 服务的。
Linq主要包含4个组件——Linq to Objects、Linq to XML、Linq to DataSet 和Linq to SQL,这4个组件对应4种查询的对象:内存集合中的对象,XML,SQL和Dataset。在我的经历中,我用的最多的是前三个,所以这里主要就是总结前三种查询。
在Linq之前查询数据,基本的方式就是循环遍历,按照条件比较,把得到的结果放到临时集合中。Linq彻底简化了这个过程,使用了简单的方式完成了查询。不管查询上述的哪些对象,方式其实还都是一样的:要么使用查询表达式,要么使用扩展方法。
以查询内存集合中的对象为例,看一下两种使用方式:
using System.Linq;
List<string> collection = new List<string>(); for (int i = 0; i < 10; i++) { collection.Add("A" + i.ToString()); } // 创建查询表达式来获得序号为偶数的元素 var queryResults = from s in collection let index = int.Parse(s.Substring(1)) where index % 2 == 0 select s; // 使用扩展方法来获得序号为偶数的元素 var queryResults1 = collection.Where(item => { int index = int.Parse(item.Substring(1)); return index % 2 == 0; }); // 输出查询结果 foreach (string s in queryResults) { Console.WriteLine(s); }
查询Xml的方式比较相似,下面来自网上的例子比较了使用原来Class访问Xml的方式,和使用XLinq访问Xml的方式:
using System.Xml; using System.Xml.Linq; static void Main(string[] args) { Console.WriteLine("使用XPath来对XML文件查询,查询结果为:"); OldLinqToXMLQuery(); Console.WriteLine("使用Linq方法来对XML文件查询,查询结果为:"); UsingLinqLinqtoXMLQuery(); Console.ReadKey(); } // 初始化XML数据 private static string xmlString = "<Persons>"+ "<Person Id='1'>"+ "<Name>张三</Name>"+ "<Age>18</Age>"+ "</Person>" + "<Person Id='2'>"+ "<Name>李四</Name>"+ "<Age>19</Age>"+ "</Person>"+ "<Person Id='3'>" + "<Name>王五</Name>" + "<Age>22</Age>" + "</Person>"+ "</Persons>"; // 使用XPath方式来对XML文件进行查询 private static void OldLinqToXMLQuery() { // 导入XML文件 XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xmlString); // 创建查询XML文件的XPath string xPath = "/Persons/Person"; // 查询Person元素 XmlNodeList querynodes = xmlDoc.SelectNodes(xPath); foreach (XmlNode node in querynodes) { // 查询名字为李四的元素 foreach (XmlNode childnode in node.ChildNodes) { if (childnode.InnerXml == "李四") { Console.WriteLine("姓名为: "+childnode.InnerXml + " Id 为:" + node.Attributes["Id"].Value); } } } } // 使用Linq 来对XML文件进行查询 private static void UsingLinqLinqtoXMLQuery() { // 导入XML XElement xmlDoc = XElement.Parse(xmlString); // 创建查询,获取姓名为“李四”的元素 var queryResults = from element in xmlDoc.Elements("Person") where element.Element("Name").Value == "李四" select element; // 输出查询结果 foreach (var xele in queryResults) { Console.WriteLine("姓名为: " + xele.Element("Name").Value + " Id 为:" + xele.Attribute("Id").Value); } }
需要注意的是使用上面Linq查询得到的结果,只有当使用foreach等方法去遍历的时候查询才会真正的执行并返回结果。
有的同学很快就发现了,这些关键字怎么看都像是Sql语句的关键字,确实,这就是语言集成查询的真正含义,查询语句可以直接在编译的时候确定语法上正确性,这一点体现最完全的就是Linq to Sql,通常也称为DLinq。Sql基本所有的关键字都可以在C#中找到想对应的关键字,所以Sql基本所有的操作在Linq中都有,比如:查询、排序、分组、增加和删除等等。这个可以通过查询MSDN(http://msdn.microsoft.com/zh-cn/library/bb397676.aspx)或者学习一些优秀的Blog(http://www.cnblogs.com/lifepoem/archive/2011/12/16/2288017.html) 来熟悉其语法。
在.NET下与数据库交互时,为了从对象级别处理问题,必须对数据库进行抽象的到对应的对象,这个就是ORM(对象关系映射,Object/Relation Mapping)的过程。ORM按照字面的意思理解即可:ORM的过程就是把数据库中的概念,比如数据库,表,字段等,处理成语言中的对象,然后在对象级别上完成数据库的常用操作,这些操作会通关相关的机制自动的反映到数据库中。这是一个抽象的过程,这样做的好处就是处理的程序不用考虑数据库实际操作上的细节,只要在语言层次上完成相关的操作,数据库自然就会被更新。
在.NET下ORM的手段主要是两种:DLinq和Entity Framework。这两种手段的目标都是一致的,就是ORM的那个过程,但是处理的细节(比如抽象的级别,支持的数据库,支持的程度,是否继续维护等等)有些不同,也就导致了两者不同的命运。总的来说,Entity Framework是重量级的工具,支持多种数据库,特性更多,更加抽象,更加灵活,效率更好一点,而且新版对linq的语法支持也越来越完善(DLinq不再添加新特性了,前途很明显),这真是居家旅行杀人越货必备之首选啊。
两者的对比网上也很多,比如:http://blog.163.com/kunkun0921@126/blog/static/169204332201401605839384/ 。
这里想多啰嗦一句的就是Entity Framework支持所谓的Database First和Code First两种开发方式,前置是先设计数据库,后映射对象;后者是先设计对象,后生成数据库。这些开发方式还是相当灵活的,而且在VS的命令行中生成、更新数据库都有相关的命令,使用起来是相当的方便。
此外还有一些好的学习网站: