微软最近的动作频繁,c#2.0规范才刚推出不久,我还没来的及完全消化c#2.0中的所有新特性,而今又推出了c#3.0,在短短几年中,微软就对c#进行了两次大的升级,为c#加入了许多新的特性,从而使c#变得更加强大也更加现代了。下面我就把c#3.0中的新特性的要点列出来,以供大家快速浏览。
第一个特性:隐式类型化本地变量
这个特性非常简单,有些javascript的影子,我们可以统一使用使用"var"关键字来声明局部变量,而不再需要指明变量的确切类型了,变量的确切类型可通过声明变量时的初始值推断出来。这样一来,可以大大简化我们声明局部变量的工作量了,下面是一个例子:
classlocalvariables:apprunner.abstractapplication
{
publicoverridevoidrun()
{
varintvalue=5;
varstringvalue="thisisastring";
varcustomclass=newlocalvariables();
varintarray=newint[3]{1,2,3};
foreach(varvalueinintarray)
console.writeline(value);
}
}
上面的代码将被解析成:
classlocalvariables:apprunner.abstractapplication
{
publicoverridevoidrun()
{
intintvalue=5;
stringstringvalue="thisisastring";
localvariablescustomclass=newlocalvariables();
int[]intarray=newint[3];
foreach(intvalueinintarray)
console.writeline(value);
}
}
要特别注意的是,由于变量的类型是通过变量初始值推断而来的,所以在声明变量的同时必需为变量指定初始值。并且,变量并不是没有类型的,变量一旦初始化之后,类型就确定下来了,以后就只能存储某种类型的值了,比如上面的stringvalue的类型经推断为string,所以该变量就只能保存string类型的值了。
第二个特性:匿名类型
有些时候我们需要临时保存一些运算的中间结果,特别是当这些中间结果是由多个部份组成时,我们常常会去声明一个新的类型,以方便保存这些中间结果。表面上看起来这很正常,而细想之后就会发现,这个新类型只服务于这个函数,其它地方都不会再使用它了,就为这一个函数而去定义一个新的类型,确实有些麻烦。
现在,c#3.0中的匿名类型特性就可以很好的解决上面提到的问题,通过匿名类型,我们可以简单使用new{属性名1=值1,属性名2=值2,.....,属性名n=值n}的形式直接在函数中创建新的类型,看下面这个例子:
classanonymoustype:apprunner.abstractapplication
{
publicoverridevoidrun()
{
varanonymoustype1=new{
cardnumber="10001",name="van’s",sex=true
};
console.writeline(anonymoustype1.cardnumber);
console.writeline(anonymoustype1.name);
varanonymoustype2=new{
cardnumber="10002",name="martin",sex=true
};
anonymoustype2=anonymoustype1;
}
}
在新类型中只能有字段成员,而且这些字段的类型也是通过初值的类型推断出来的。如果在声明新的匿名类型时,新类型的字段名、顺序以及初始值的类型是一致的,那么将会产生相同的匿名类型,所以上例中anonymoustype1和anonymoustype2的类型是相同的,自然能进行anonymoustype2=anonymoustype1的赋值。
第三个特性:隐式类型化数组
这个特性是对隐式类型化本地变量的扩展,有了这个特性,将使我们创建数组的工作变得简单。我们可以直接使用"new[]"关键字来声明数组,后面跟上数组的初始值列表。在这里,我们并没有直接指定数组的类型,数组的类型是由初始化列表推断出来的。
classanonymoustypearray:apprunner.abstractapplication
{
publicoverridevoidrun()
{
varintarray=new[]{1,2,3,4,5};
vardoublearray=new[]{3.14,1.414};
varanonymoustypearray=new[]{
new{name="van’s",sex=false,arg=22},
new{name="martin",sex=true,arg=23}
};
console.writeline(intarray);
console.writeline(doublearray);
console.writeline(anonymoustypearray[0].name);
}
}
上面的代码中,anonymoustypearray变量的声明同时运用了隐式类型化数组和匿名类型两种特性,首先创建匿名类型,然后再初始值列表,推断出数组的确切类型。
第四个特性:对象构造者
我们在声明数组时,可以同时对其进行初始化,这样就省去了很多麻烦,但是在创建类的对象时,这招可就不灵了,我们要么调用该类的构造函数完成对象的初始化,要么就手工进行初始化。这两种方法都不太方便,使用构造函数来对对象进行初始化时,我们为了某种灵活性,可能需要编写构造函数的多个重载版本,实在是麻烦。
c#3.0中加入的对象构造者特性,使得对象的初始化工作变得格外简单,我们可以采用类似于数组初始化的方式来初始化类的对象,方法就是直接在创建类对象的表达式后面跟上类成员的初始化代码。具体示例如下:
classpoint
{
publicintx{get;set;}
publicinty{get;set;}
publicoverridestringtostring()
{
return"("+x.tostring()+","+y.tostring()+")";
}
}
classrectangle
{
publicpointp1{get;set;}
publicpointp2{get;set;}
publicrectangle()
{
p1=newpoint();
p2=newpoint();
}
publicoverridestringtostring()
{
return"p1:"+p1+",p2:"+p2;
}
}
classobjectbuilder:apprunner.abstractapplication
{
publicoverridevoidrun()
{
pointthepoint=newpoint(){x=1,y=2};
console.writeline("point(x,y)=",thepoint);
rectangletherectangle=newrectangle(){
p1={x=1,y=1},p2={x=100,y=200}
};
console.writeline(therectangle);
}
}
我们在定义point类的x和y属性时,只须写上该属性的get和set访问器声明,c#编译器会自动为我们生成默认的get和set操作代码,当我们需要定义简单属性时,这个特性非常有用。
我们以newpoint(){x=1,y=2}语句,轻松的完成了对point类的初始化工作。在创建类的对象时,我们可以按照需要去初始化类的对象,只要在类的创建表达式后跟上要初始化属性的列表即可,且可以只对需要初始化的属性赋初值,而无需把所有属性的初始值都写上去。
在therectangle对象的初始化表达式中,我们首先对p1属性进行初始化,然而p1属性也是一个自定义的类型,所以p1属性的初始化是另一个类型(point)的初始化表达式,我们可以这样的方式来对更加复杂的类型进行初始化。
上篇文章中介绍了c#3.0中比较简单的四个特性,分别是隐式类型化本地变量、匿名类型、隐式类型化数组,以及对象构造者,下面我将对c#3.0中的较复杂,同时也是非常强大的几个特性进行介绍,供大家快速浏览。
第五个特性:集合构造者
我们可以在声明数组的同时,为其指定初始值,方法是直接在数组声明的后面跟上初始值列表。这样就使数组的初始化工作变得简单,而对于我们自己创建的集合类型,就无法享受到与普通数组一样的待遇了,我们无法在创建自定义集合对象的同时,使用数组的初始化语法为其指定初始值。
c#3.0中加入的集合构造者特性,可使我们享受到与普通数组一样的待遇,从而在创建集合对象的同时为其指定初始值。为了做到这一点,我们需要让我们的集合实现icollection<t>接口,在这个接口中,完成初始化操作的关键在于add函数,当我使用初始化语法为集合指定初始值时,c#编译器将自动调用icollection<t>中的add函数将初始列表中的所有元素加入到集合中,以完成集合的初始化操作。使用示例如下:
classcollectioninitializer:apprunner.abstractapplication
{
classstringcollection:icollection<string>
{
publicvoidadd(stringitem)
{
console.writeline(item);
}
//othericollection<t>members
}
publicoverridevoidrun()
{
stringcollectionstrings=newstringcollection(){"van's","brog","vicky"};
}
}
在这个示例中,编译器会自动为strings对象调用add方法,以将初始值列表中的所有元素加入到集合中,这里我们只是简单将初始值列表中的元素输出到控制台。
第六个特性:lambda表达式
c#2.0中加入的匿名代理,简化了我们编写事件处理函数的工作,使我们不再需要单独声明一个函数来与事件绑定,只需要使用delegate关键字在线编写事件处理代码。
而c#3.0则更进一步,通过lambda表达式,我们可以一种更为简洁方式编写事件处理代码,新的lambda事件处理代码看上去就像一个计算表达式,它使用"=>"符号来连接事件参数和事件处理代码。我可以这样写:someevent+=事件参数=>事件处理代码;下面是完整的示例:
delegatetadddelegate<t>(ta,tb);
classlambdaexpression:apprunner.abstractapplication
{
publicstaticeventeventhandlermyevent;
publicoverridevoidrun()
{
myevent+=delegate(objects,eventargse)
{
console.writeline(s);
};
myevent+=(s,e)=>{console.writeline(s);};
myevent(this,null);
adddelegate<string>add=(a,b)=>a+b;
console.writeline(add("lambda","expression"));
}
}
在上面的例子中,分别使用了匿名代理和lambda表达式来实现同样的功能,可以明显看出lambda表达式的实现更为简洁。我们在使用lambda表达式编写事件处理代码时,无需指明事件参数的类型,且返回值就是最后一条语句的执行结果。
第七个特性:扩展方法
当我们需要对已有类的功能进行扩展时,我们通常会想到继承,继承已有类,然后为其加入新的行为。而c#3.0中加入的扩展方法特性,则提供了另一种实现功能扩展的方式,我们可以在不使用继承的前提下实现对已有类本身的扩展,这种方法并不会产生新的类型,而是采用向已有类中加入新方法的方式来完成功能扩展。
在对已有类进行扩展时,我们需将所有扩展方法都写在一个静态类中,这个静态类就相当于存放扩展方法的容器,所有的扩展方法都可以写在这里面。而且扩展方法采用一种全新的声明方式:publicstatic返回类型扩展方法名(this要扩展的类型sourceobj[,扩展方法参数列表]),与普通方法声明方式不同,扩展方法的第一个参数以this关键字开始,后跟被扩展的类型名,然后才是真正的参数列表。下面是使用示例:
staticclassextensions
{
publicstaticinttoint32(thisstringsource)
{
returnint32.parse(source);
}
publicstatict[]slice<t>(thist[]source,intindex,intcount)
{
if(index<0||count<0||index+count>source.length)
{
thrownewargumentexception();
}
t[]result=newt[count];
array.copy(source,index,result,0,count);
returnresult;
}
}
classextensionmethods:apprunner.abstractapplication
{
publicoverridevoidrun()
{
stringnumber="123";
console.writeline(number.toint32());
int[]intarray=newint[]{1,2,3};
intarray=intarray.slice(1,2);
foreach(variinintarray)
console.writeline(i);
}
}
在上面的示例中,静态的extensions类中有两个扩展方法,第一个方法是对string类的扩展,它为string类加入了名为toint32的方法,该方法没有参数,并返回一个int类型的值,它将完成数字字符向整数的转换。有了这个扩展方法之后,就可对任意string类的对象调用toint32方法了,该方法就像其本身定义的一样。
第二个扩展方法是一个范型方法,它是对所有数组类型的扩展,该方法完成数组的切片操作。
c#3.0中的linq表达式,就是大量运用扩展方法来实现数据查询的。
第八个特性:linq查询表达式
c#3.0中加入的最为复杂的特性就是linq查询表达式了,这使我们可直接采用类似于sql的语法对集合进行查询,这就使我们可以享受到关系数据查询的强大功能。
linq查询表达式是建立在多种c#3.0的新特性之上的,这也是我为什么最后才介绍linq的原因。下面看一个例子:
classlinqexpression:apprunner.abstractapplication
{
publicoverridevoidrun()
{
//定义匿名数组persons,并为其赋初值
varpersons=new[]{
new{name="van's",sex=false,age=22},
new{name="martin",sex=true,age=30},
new{name="jerry",sex=false,age=24},
new{name="brog",sex=false,age=25},
new{name="vicky",sex=true,age=20}
};
/*
执行简单linq查询
检索所有年龄在24岁以内的人
查询结果放在results变量中
results变量的类型与数组persons相同
*/
varresults=frompinpersons
wherep.age<=24
selectp;
foreach(varpersoninresults)
{
console.writeline(person.name);
}
console.writeline();
//定义匿名数组customers,并为其赋初值
//该数组是匿名类型的
varcustomers=new[]{
new{
name="van's",city="china",orders=new[]{
new{
orderno=0,
ordername="c#programminglanguage(secondedition)",
orderdate=newdatetime(2007,9,5)
},
new{
orderno=1,
ordername="headfirstdesignpatterns(chineseedition)",
orderdate=newdatetime(2007,9,15)
},
new{
orderno=2,
ordername="asp.netunleashed2.0(chineseedition)",
orderdate=newdatetime(2007,09,18)
},
new{
orderno=3,
ordername="thec++programminglangauge(specialedition)",
orderdate=newdatetime(2002,9,20)
}
}
},
new{
name="brog",city="china",orders=new[]{
new{
orderno=0,
ordername="c#programminglanguage(secondedition)",
orderdate=newdatetime(2007,9,15)
}
}
},
new{
name="vicky",city="london",orders=new[]{
new{orderno=0,
ordername="c++programminglanguage(specialedition)",
orderdate=newdatetime(2007,9,20)
}
}
}
};
/*
执行多重linq查询
检索所在城市为中国,且订单日期为2007年以后的所有记录
查询结果是一个匿名类型的数组
其中包含客户名,订单号,订单日期,订单名四个字段
*/
varsomecustomers=fromcincustomers
wherec.city=="china"
fromoinc.orders
whereo.orderdate.year>=2007
selectnew{c.name,o.orderno,o.orderdate,o.ordername};
foreach(varcustomerinsomecustomers)
{
console.writeline(
customer.name+","+customer.ordername+","+
customer.orderdate.tostring("d")
);
}
}
}
从上面的例子中,我们可以看到linq查询的强大特性,它允许我们进行简单查询,或者进行更为复杂的多重连接查询。且查询的结果还可以是自定义的匿名类型。
以上是对c#3.0中新增的八大特性的简要介绍,如果想了解更为深入的内容,可查看c#3.0的官方规范。