主题:C# 3.0 特性预览
jzyray
[专家分:20610] 发布于 2006-05-20 23:33:00
声明:本文主要是根据微软《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)查询语言的代码。
回复列表 (共9个回复)
沙发
jzyray [专家分:20610] 发布于 2006-05-20 23:34:00
一、隐式局部变量
在以前,如果我们要声明局部变量,比如:
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、扩展方法在作为附加方法调用时的优先级比同名的实例方法要低。即编译器默认调用实例类型的原有方法,如果找不到泽调用扩展方法。
板凳
jzyray [专家分:20610] 发布于 2006-05-20 23:35:00
三、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);
表达式树的这种数据形式在后面将要谈到的数据查询方面是一个很好的辅助工具。
3 楼
jzyray [专家分:20610] 发布于 2006-05-20 23:35:00
五、对象和集合初始化器
使用对象初始化表达式可以方便地初始化一个或者多个对象的属性或者域。
初始化表达式由“{”和“}”进行封闭,同时内部的成员初始化器用“,”进行分隔。成员初始化器包括成员名称、“=”和初始化的值。
比如,对于类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。
4 楼
jzyray [专家分:20610] 发布于 2006-05-20 23:35:00
八、查询表达式
个人认为,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语法,不过经过嵌套、组合等方法当然可以形成复杂的表达式,这就靠大家今后巩固了。
5 楼
jzyray [专家分:20610] 发布于 2006-05-20 23:36:00
附:
《C# 3.0 Specification》
[url=http://download.microsoft.com/download/9/5/0/9503e33e-fde6-4aed-b5d0-ffe749822f1b/csharp%203.0%20specification.doc]http://download.microsoft.com/download/9/5/0/9503e33e-fde6-4aed-b5d0-ffe749822f1b/csharp%203.0%20specification.doc[/url]
全文已经整理到Blog中
[url=http://blog.programfan.com/article.asp?id=14415]http://blog.programfan.com/article.asp?id=14415[/url]
6 楼
jzyray [专家分:20610] 发布于 2006-05-20 23:36:00
(ps.多占一楼……计算失误~~~~~~)
7 楼
toicq2002 [专家分:180] 发布于 2008-01-25 22:12:00
太漂亮了
8 楼
jzyray [专家分:20610] 发布于 2008-01-25 22:44:00
汗。。。楼上的签名是怎么个情况……
9 楼
灵雨飘零 [专家分:0] 发布于 2008-02-07 11:20:00
好
好帖
很好帖
确实好帖
少见的好帖
真 你好 好帖
难得一见的好帖
千年等一回的好帖
好得不能再好的好帖
惊天地且泣鬼神的好帖
让人阅毕击掌三叹的好帖
让人佩服得五体投地的好帖
让人奔走相告曰须阅读的好帖
让斑竹看后决定加精固顶的好帖
让人看后在各论坛纷纷转贴的好帖
让人看后连成人网站都没兴趣的好帖
让人看完后就要往上顶往死里顶的好帖
让人看后不断在各种场合重复引用的好帖
让人一见面就问你看过某某好帖没有的好帖
让人半夜上厕所都要打开电脑再看一遍的好帖
让个读过后都下载在硬盘里详细研究欣赏的好帖
让人走路吃饭睡觉干什么事连做梦都梦到它的好帖
让人翻译成36种不同外语流传国内外世界各地的好帖
让人纷纷唱道过年过节不送礼要送就送某某帖子的好帖
让国家领导人命令将该帖刻在纯金版上当国礼送人的好帖
让网络上纷纷冒出该帖的真人版卡通版搞笑版成人版的好帖
让人在公共厕所里不再乱涂乱化而是纷纷对它引经据典的好帖
让某位想成名的少女向媒体说她与该帖作者发生过性关系的好帖
让人根据它写成小说又被不同导演拍成48个不同版本的电影的好帖
让某名导演跟据此帖改拍的电影在奥斯卡上一连拿了11个奖项的好帖
让人大代表们看完后联名要求根据该帖的内容对宪法做适当修改的好帖
让人为了谁是它的原始作者纷纷地闹上法院打官司要争得它的版权的好帖
让各大学府纷纷邀请该帖作者去就如何发表优秀网络文学为题目演讲的好帖
让人为了该帖而成立了各种学会来研究并为不同的理解争得眼红脖子粗的好帖
让美国警察于今后逮捕人说你有权保持沉默还有权阅读某某帖子要不要啊的好帖
让本拉登躲在山洞里还命令他手下冒着被美军发现的危险去上网下载来阅读的好帖
让萨达姆被捕时被发现他随身携带的除了一把手枪之外还有的就是它的复印件的好帖
让比尔盖茨在懂事会上发给与会者人手一份该帖命令仔细阅读后才讨论其他事宜的好帖
让诺贝儿奖理事会破天荒地因该帖的出现而开会讨论一直决定今后设立最佳帖子奖的好帖
让联合国安理会决定将它译成宇宙语由中国神州六号升空后不断播放看有没有外星人的好帖
让人看完后IE锁死连瑞星诺顿都没法修复只好格式化硬盘重装启动后主页显示的还是它的好帖
我来回复