c#3.0 特性
声明:本文主要是根据微软《C# 3.0 Specification》文档翻译而成(本文内容并非完整按照该文档进行编排),并对msdn相关文档中的资料进行整理而成。若有翻译不妥之处,恳请指正。
阅读本文前,需要了解:
1、C# 3.0代号“Orcas”,是基于C# 2.0的扩展。提供了多种具有更高层次功能的类库。这些扩展允许构造组合(compositioanl)API来实现具有同关系型数据库及XML等领域相等效能的表达效力。
2、LINQ项目可以看作是一个未来技术的演示项目,可以从MSDN网站上下载预览包。LINQ项目旨在扩展C#及VB.NET在语法上对语言集成查询的支持。借助这些特性,我们可以用类似SQL或者XQuery之类的语句进行代码编写。LINQ项目的内容不单独介绍,因为它对于C#中的特性主要就是C# 3.0中的语言集成查询特性。
3、写这篇文章的目的很简单,就是希望有兴趣的朋友可以开始3.0的探索了,这样当3.0的编译器出台时不至于再赶时间学习。并不建议初学者花费精力来掌握本文内容,了解一下发展概况即可,否则很容易导致两头都搞不好。况且这并不是最基础的内容。
4、本文内容仅基于预览版本内容(PDC 2005 Technology Preview),并非最终版本。C# 3.0完成后,有可能会增加或者更改某些特性。预览版本可能还不能支持C# 3.0中的某些内容,对于这些内容,将简单介绍。
5、本文旨在将新的特性展现出来,针对每个特性并不进行深入的探讨,读者如果有兴趣可以自行参阅相关资料。
那么,我们就开始吧。
C# 3.0的扩展特性主要包括以下几点,我们在后面也会按照这个顺序进行介绍:
1、隐式局部变量(implicitly typed local variables),通过初始化该局部变量的表达式自动推断出该变量的类型。
2、扩展方法(extention methods),可以利用附加方法拓展已经存在的类型和构造类型。
3、Lambda表达式(lambda expressions),匿名方法的革新,能够提供更好的类型推导以及到委托类型和表达式树的转换。
4、表达式树(expression trees),允许Lambda表达式以数据(表达式树)的形式存在,而不是代码(委托)。
5、对象初始化器(object initializer),简化了对象的构造和初始化。
6、匿名类型(anonymouse types),由对象初始化器自动推断和生成的元组类型。
7、隐式数组(implicitly typed array),一种数组创建和初始化的形式,可以从数组初始化器推导出数组的元素类型。
8、查询表达式(query expressions),提供语言集成查询的语法,使得在编程中可以使用类似关系型(如SQL)以及层次(如XQuery)查询语言的代码。
一、隐式局部变量
在以前,如果我们要声明局部变量,比如:
int i = 5;
string str = "test";
int[] numberss = new int[]{1, 2, 3);
ArrayList list = new ArrayList();
在C# 3.0,完全可以等价为
var i = 5;
var str = "test";
var numberss = new int[]{1, 2, 3);
var list = new ArrayList();
我们可以看到,用一个简单的var就可以代替这么些类型(怀念Pascal中~~~)。因为,我们往往只要关注后面的赋值表达式即可推断出变量的类型,在这里也是如此。
不过呢,自然要有运用条件:
1、出于向后兼容的考虑,如果作用域中出现一个叫做var的类型,那么会优先使用var类型来声明该变量而不再推断表达式的类型。
2、声明隐式变量的同时需要初始化。而初始化需要一个表达式,且该表达式不能是单独的对象或者集合初始化器,但可以是包含对象或者集合初始化器的new表达式。如var x;和var x = {1 ,2 ,3};都是错误的。
3、表达式的编译时类型不能为空类型(null)。如var x = null;就是错误的。
4、如果隐式变量的声明中包含多个声明符,那么这些声明符必须具备同样的编译时类型。
此外,for语句中的初始化部分以及using语句的资源声明中,都可以使用隐式变量。同样的,使用foreach迭代也可以利用隐式变量:
int[] numbers = {1, 2, 3, 4};
foreach(var n in numbers) Console.WriteLine(n);
二、扩展方法
通过在方法的第一个参数中用this关键字修饰,即可声明一个扩展方法。但是,扩展方法必须声明于静态类中。
namespace TestSpace{
public static class Extensions{
public static int ToInt32(this string s){
return int.Parse(s);
}
}
}
这些扩展方法享有普通静态方法等同的功能。此外,只要引入了扩展方法,我们就可以通过普通的实例方法调用语法来调用它们。
那么,我们现在就介绍如何引入扩展方法。
相信大家对于using很熟悉了吧。using System;相当于引入了System命名空间中的所有类型。不过呢,using在这里的意义可不单单是引入了类型,还引入了属于该命名空间下的扩展方法。扩展方法被引入之后,扩展方法中的第一个参数(即被this修饰的参数)的类型便多了这么一个附加方法。并且该附加方法的优先级比该类型的同名实例方法要低。比如要使用上例中的扩展方法,先引入:using TestSpace;然后可以这样调用:
string str = "1234";
int re = str.ToInt32();
//上式等价于int re = Extentions.ToInt32(s);
经过这两个例子,我们可以看出:
1、扩展方法必须声明在静态类中(这样也能保证自身是静态方法)。
2、扩展方法声明时至少有一个参数,即要附加的实例类型,并且要用this来修饰。
3、扩展方法在作为附加方法调用时必然成为实例方法。
4、扩展方法在作为附加方法调用时的优先级比同名的实例方法要低。即编译器默认调用实例类型的原有方法,如果找不到泽调用扩展方法。
三、Lambda表达式
通过C# 2.0带来的匿名方法,我们已经发现了简化代码的一种方法。但是,这样的做法仍然有些冗长且具有强制性,所以C# 3.0中又引入了Lambda表达式。
Lambda表达式写成参数列表形式,后面紧跟“=>”符号,再跟上一个表达式或者代码块。只要看一下后面给出的例子,Lambda表达式的写法就很容易掌握,所以这里不再给出完整的Lambda表达式定义式。
并且,Lambda表达式中的参数可以是显式,也可以是隐式的。显式参数列表中参数类型是显式指定的,而隐式参数列表中的参数类型会根据上下文而定(特别地,当Lambda表达式转换为兼容委托时,由这个委托提供参数的具体类型)。
此外,如果参数列表中只有一个隐式的参数,那么圆括号可以省略,如:
( param ) => expression
可简化为
param => expression
那么,我们来看一下例子:
(int x) => x + 1 //显式参数列表
x => x + 1 //隐式参数列表,由于只有一个隐式的参数,所以没有圆括号
() => Console.WriteLine("Hello") //无参数
(int x, int y) => x + y //两个显式参数
x => { return x + 1; } /*带有代码块的隐式Lambda表达式。但该功能目前尚不被PDC 2005 Technology Preview所支持,所以下文中将不再讨论该功能的运用。*/
相信大家看完这些例子以后就知道怎么使用Lambda表达式了吧(注意:这里说的只是表达式,仅是语句的一部分,相信细心的同志们已经发现例子中没有结尾分号了吧)。
然后我们看一下将Lambda表达式融入语句中的示例,同时给出用匿名方法实现的等效做法来作对比(其中利用Func泛型委托):
//sample 1
Func<int, int> add = x => x + 1; //隐式表达式
//等价于
Func<int, int> add =
delegate(int x){
return x + 1;
};
//sample 2
Func<string, bool> filter = s => s == "123"; //隐式表达式
//等价于
Func<string, bool> filter =
delegate(string s){
return s == "123";
};
由此可以看出,Lambda表达式简化了的确不少。更重要的是,它可以将一段匿名方法简化为一个表达式,这样就为了嵌套使用提供了保障,我在后面的其它部分中也会涉及到嵌套调用。
四、表达式树
表达式树并不是PDC 2005标准的一部分,这里将简单阐述。
表达式树最根本的运用就是System.Query.Expression<D>类型(有的材料上是System.Expressions.Expression<T>)。它为Lambda表达式提供了一个内存中高效的数据表示形式。
对于下面这个Lambda表达式:
Func<int, int> f = x => x + 1;
这是代码(委托)形式。而
Expression<Func<int, int>> e = x => x + 1;
就是一个对表达式树的引用。
Lambda表达式可以直接执行,如int re = f(1);
但是表达式树的引用不可以,即int re = e(1);是错误的。
那么如何运用表达式树呢?
表达式树可以看作是对表达式委托的结构体现。看看下面这个例子(转),估计就比较有些理解了:
Expression<Func<int, boo>> filter = n => n < 5;
BinaryExpression body = (BinaryExpression)filter.Body;
ParameterExpression left = (ParameterExpression)body.Left;
ConstantExpression right = (ConstantExpression)body.Right;
Console.WriteLine("{0} {1} {2}", left.Name, body.NodeType, right.Value);
表达式树的这种数据形式在后面将要谈到的数据查询方面是一个很好的辅助工具。
五、对象和集合初始化器
使用对象初始化表达式可以方便地初始化一个或者多个对象的属性或者域。
初始化表达式由“{”和“}”进行封闭,同时内部的成员初始化器用“,”进行分隔。成员初始化器包括成员名称、“=”和初始化的值。
比如,对于类Dog:
public class Dog{
private string name;
private int age;
public string Name{
get{
return name;
}
set{
name=value;
}
}
public int Age{
get{
return age;
}
set{
age=value;
}
}
}
这样初始化:
Dog dog = new Dog{
Name = "Toddy", Age = 5
};
等价于
Dog dog = new Dog();
dog.Name = "Toddy";
dog.Age = 5;
当然,嵌套的初始化也是允许的。
同样,集合初始化器使得集合可以像数组那样初始化:
List<int> list = new List<int>{1, 2, 3, 4};
就这么简单……
六、匿名类型
基于我们往往注重一个类型的结构而不是名称这个事实,C# 3.0引入了匿名类型。
隐式局部变量由编译器自动推测变量的类型,而匿名类型是让编译器自动推测初始化表达式的类型。
如第5节中的Dog类,我们其实也可以利用匿名类型来进行初始化:
var dog = new {Name = "Toddy", Age = 5};
我们可以看到,上例中省略了类型名。
有了匿名类型,我们无需了解这个类型叫做什么名字,只要符合这个结构即可。
七、隐式数组
从前面的种种特性中可以看到,利用编译器根据上下文推导来识别是C# 3.0的很多更新的基础,本节的隐式数组也是如此。
隐式数组会自动根据数组内容来识别数组类型。
我们以前这样写:
int[] arr = new int[]{1,2,3};
现在可以这样:
var arr = new[] {1,2,3}; //一定要有[]操作符,参见隐式局部变量的注意部分
但是必须要注意:
1、数组中的内容均可以隐性转换为某一类型。
2、最终类型不可为null。
八、查询表达式
个人认为,C# 3.0这些新特性的引入,不单单是为了在某种程度上简化平时的代码开发量。更重要的是,它们为查询表达式的方方面面都提供了良好的基础。
查询表达式能够在语言中使用类似SQL或者XPath/XQuery的语法进行数据查询。
查询表达式以from子句开始,以select或者group子句结束。大致组成顺序是(可选):from子句、where子句、orderby子句、select子句和where子句以及into子句。
看一个例子:
string[] Weekdays = {
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
};
IEnumerable<string> enum_day =
from day in Weekdays
where day[0] == 'T'
orderby day
select day.ToUpper();
foreach(var s in enum_day){
Console.WriteLine(s);
}
你将会得到:
THURSDAY
TUESDAY
如何?Amazing!
那么,这样的语句为什么能够编译呢?
其实,编译器在编译的时候进行了转换。上例中的enum_day的定义部分可转换为:
IEnumerable<string> enum_day = Weekdays
.Where(day => day[0] == 'T')
.OrderBy(day => day)
.Select(day => day.ToUpper());
这样就清楚了吧(“括号里面的表达式什么?”不会吧,复习一下Lambda表达式吧)。
当然了,像SQL这样的语句进行嵌套、组合等可以产生很多有趣的查询方法,C# 3.0中的查询方式虽然仅是简化版的SQL语法,不过经过嵌套、组合等方法当然可以形成复杂的表达式,这就靠大家今后巩固了。
附:
《C# 3.0 Specification》
http://download.microsoft.com/download/9/5/0/9503e33e-fde6-4aed-b5d0-ffe749822f1b/csharp%203.0%20specification.doc