C# 3.0 之新特性总结
C# 3.0 之新特性总结
LazyBee
现在VS2008已经发布,而且很多人也都开始使用C# 3.0了,我在这里对其新特性做一个总结,以方便自己日后或同仁参考:
1 隐式类型本地变量(var)
这个和以前VB6中的全能类型var使用了同样的名字,但在C#中,其实var不是一个实际类型,可以说是一个标记,就是让编译器去根据初始化的内容来确定需要使用的合适的类型。例如:var i = 5; 就等效于int i=5;
不过,隐式变量有以下几个限制:
1 隐式本地变量的声明必须同时包含一个初始化器。
2 初始化器必须是一个表达式,并且不能是对象或集合初始化器,但可以是包括new关键字以及对象或集合初始化器的组合表达式。例如:{1,2,3}是一个集合初始化器,如果你将其赋给一个隐式变量将出现编译错误。(var x={1,2,3}; //错误),但是你可以这样这样var x=new List<int>{1,2,3};是允许的。
3初始化器表达式在编译时不能为null.(例如:var i=null; //错误:不能为null)
4 由于是隐式本地变量,顾名思义,只能用于本地变量(在方法或属性中使用的变量).
5 如果在隐式类型本地变量的初始化器的表达式中包含其他声明变量(包括隐式类型的),那么这些变量必须是相同(或者可以隐式转换成相同)的编译时类型。
例如:var arrayD = new[] { 1, "one", 2, "two" };// 错误,不允许混合类型
var n = "java"; var m = 4; var ff = m + n; //正确
注意:foreanch中使用也是允许的,如:
int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);
2 对象初始化器
对象初始化器用于指定对象的一个或多个可访问的字段或属性的值,通过{和}进行封闭起来,多个字段赋值之间通过逗号分割。具体语法元素是:
Ø 对象创建表达式:
new 类型(类型参数可选)对象或集合初始化器可选
new 类型 对象或集合初始化器
Ø 对象或集合初始化器:
对象初始化器
集合初始化器
Ø 对象初始化器:
{对象成员初始化器列表}
Ø 对象成员初始化器列表:
对象成员初始化器
对象成员初始化器列表,对象成员初始化器
Ø 对象成员初始化器:
标识符 = 初始化值
Ø 初始化值:
表达式
对象或成员初始化器
注意:对象或成员初始化器是可以嵌套的,并且初始化器是不包含new关键字的。初始化器不能用于结构,初始化器中每个成员最多只能初始化一次。示例:
2 {
3 public int X { get; set; }
4 public int Y { get; set; }
5 }
6 public class Rectangle
7 {
8 Point p1 = new Point{ X = 1,Y = 2 };
9 Point p2 = new Point{X=3,Y=3};
10 public Point P1 { get { return p1; } set { p1 = value; } }
11 public Point P2 { get { return p2; } set { p2 = value; } }
12}
13Rectangle r2 = new Rectangle() { P1 =new Point { X = 5, Y = 6 }, P2 = { X = 7, Y = 8 } };
14
请注意r2的初始化中的不同。
3 集合初始化器
语法表示:
Ø 集合初始化器:
{元素初始化器列表}
Ø 元素初始化器列表:
元素初始化器
元素初始化器列表,元素初始化器
Ø 元素初始化器:
非赋值表达式
示例:2 public class Contact
3 {
4 string name;
5 List<string> phoneNumbers = new List<string>();
6 public string Name { get { return name; } set { name = value; } }
7 public List<string> PhoneNumbers { get { return phoneNumbers; } }
8 }
9 var contacts = new List<Contact> { new Contact {Name = "Chris Smith",PhoneNumbers = { "206-555-0101", "425-882-8080" }},
10 new Contact {Name = "Bob Harris",PhoneNumbers = { "650-555-0199" }}};
11
4匿名类型
C#3.0允许将new和一个匿名对象初始化器一起来创建一个匿名类型的对象。具体语法格式为:
Ø 匿名对象创建表达式:
new 匿名对象初始化器
Ø 匿名对象初始化器:
{成员声明器列表}
Ø 成员声明器列表:
成员声明器
成员声明器列表,成员声明器
Ø 成员声明器:
简单名称
成员访问
标识符=表达式(赋值表达式)
注:简单名称就是已经定义在当前范围可访问的标识符(变量名)。成员访问就是通过“.”分割的表达式。
实例:
Rectangle r = new Rectangle() { P1 = { X = 5, Y = 6 }, P2 = { X = 7, Y = 8 } };
var anyType = new { Name = "Hello"/*赋值表达式*/, r.P1/*成员访问*/};//匿名类型包含Name和P1两个只读成员
var testType = new { r/*简单名称*/, i = 0 };//匿名类型包含r和i两个只读成员
注意:1 匿名类型中的成员都是只读的。
2 成员的类型根据赋值表达式来推导出实际类型(赋值表达式在编译时不能为null),不能在代码中指定成员类型。
3 如果两个匿名类型创建表达式的成员变量名相同,并且经过推导出的变量的类型和顺序也相同,那么这两个匿名类型对象是两个同一匿名类
型的实例,例如:
var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;
5 隐式类型数组
隐式类型数组定义语法为:
Ø 数组创建表达式:
new [] 数组初始化器
隐式类型数组实例的类型是根据数组初始化器推导出来的,所以这个数组初始化器只能包含确切的一种类型(也可以包含可以隐式转换成这种类型的类型),而且这个类型不是null,否则将产生一个编译错误。
var a = new[] { 1, 10, 100, 1000 }; // int[]
var b = new[] { 1, 1.5, 2, 2.5 }; // double[]
var c = new[] { "hello", null, "world"}; // string[]
var d = new[] { 1, "one", 2, "two" }; // 编译错误,int和string不能进行隐式转换,这种情况必须显示的指定数组类型,如object[]
隐式类型数组可以和匿名类型一块使用来创建匿名类型的数据结构:如:
var contacts = new[] { new {Name = "Chris Smith",PhoneNumbers = new[]{ "206-555-0101", "425-882-8080" }},
new {Name = "Bob Harris",PhoneNumbers = new[] { "650-555-0199" }}};
6 扩展方法:
{
public static class Extensions
{
public static int ToInt32(this string s)
{ return Int32.Parse(s);}
public static T[] Slice<T>(this T[] source, int index, int count)
{
if (index < 0 || count < 0 || source.Length-index < count) throw new ArgumentException();
T[] result = new T[count];
Array.Copy(source, index, result, 0, count);
return result;
}
}
}
然后在你要调用扩展方法的地方导入LazyBee.Utilities命名空间,就可以象如下方式调用:
string s = "1234";
int i = s.ToInt32(); // Same as Extensions.ToInt32(s)
int[] digits = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int[] e = digits.Slice(4, 3); // Same as Extensions.Slice(digits, 4, 3)
注意:
1 实例方法优先于扩展方法的调用,也就是说只有不存在符合要求的实例方法,才会去查找时候存在扩展方法。
2 扩展方法具有一般静态方法所拥有的一切特性。
3 扩展方法比实例方法具有更多的限制,以及更低的优先级。所以要尽量少用扩展方法,建议只有在不能增加实例方法的情况下使用。
4 其他类型的扩展成员如属性、事件、操作目前不支持,不过微软正在考虑当中。
7 Lambda表达式:
6.1 Lambda表达式提供一种更精简的方式来写匿名方法,其语法格式为:
Ø 表达式:
赋值表达式
非赋值表达式
Ø 非赋值表达式:
条件表达式
Lambda表达式
查询表达式
Ø Lambda表达式:
(Lambda参数列表可选)=>Lambda表达式体
隐式类型Lambda参数=> Lambda表达式体
Ø Lambda参数列表:
显式类型Lambda参数列表
隐式类型Lambda参数列表
Ø 显式类型Lambda参数列表:
显式类型Lambda参数
显式类型Lambda参数列表,显式类型Lambda参数
Ø 显式类型Lambda参数:
参数修饰符可选 类型 标识符
Ø 隐式类型Lambda参数列表
隐式类型Lambda参数
隐式类型Lambda参数列表,隐式类型Lambda参数
Ø 隐式类型Lambda参数:
标识符
Ø Lambda表达式体:
表达式
语句块
示例:
x => x + 1; // Implicitly typed, expression body
x => { return x + 1; }; // Implicitly typed, statement body
(int x) => x + 1; // Explicitly typed, expression body
(int x) => { return x + 1; }; // Explicitly typed, statement body
() => Console.WriteLine("OK"); //无参数列表
(x,y) => x*y; // Multiple parameters
注意:1 如果是隐式类型,并且只有一个参数时,可以省略括号,如(x) => x + 1,可以简写成x => x + 1
2 隐式类型和显式类型不能混合使用
6.2 当代理类型和Lambda表达式满足下列条件时,我们说它们是兼容的,也就是可以将Lambda表达式赋值给该代理类型:
1 代理类型和Lambda表达式具有相通数量的参数;
2 如果Lambda表达式是显式类型参数列表,那么每一个参数的类型要和对应的代理类型的参数的类型和修饰符相同;如果是隐式类型参数列表,那么代理类型中不能有ref和out的参数,根据Lambda表达式体推导出的参数类型要和代理类型的参数一致。
3 如果Lambda表达式体是一个语句块,并且代理类型的返回值是void,那么语句块中不能包含有包含返回值的return语句;如果代理类型的返回值不是void,那么语句块必须是包含返回值的return语句,而且返回值的类型要和代理类型的返回值一致。
4 如果Lambda表达式体是一个表达式,并且代理类型的返回值是void,那么表达式必须是能作为语句的表达式(只能是赋值表达式,对对象或函数的调用,new 表达式,变量的++,--操作表达式之一);如果代理类型的返回值不是void,那么根据表达式推导出的类型必须能和代理类型的返回值类型一致。
示例:
public delegate R Func<T,R>(T t);
public delegate void F2();
public delegate R F3<T,R>(T t,R r);
Func<int, int> a = x => x + 1;
F2 b = () => Console.WriteLine("OK");
F3<int, int> c = (x,y) => x*y;
下例将会出现编译错误:
public delegate void F3<T,R>(T t,R r);
F3<int, int> c = (x,y) => x*y;//Compile error: Only assignment, call, increment, decrement, and new object expressions can be used as a statement
6.3 当Lambda表达式传递给一个没有指定类型参数的泛型方法时,.Net架构将根据表达式来推导出相应的类型参数,例如:
public static class Extensions
{
public static IEnumerable<S> MySelect<T, S>(this IEnumerable<T> source,Func<T, S> selector)
{ foreach (T element in source) yield return selector(element); }
}
var contacts = new[] { new {Name = "Chris Smith",PhoneNumbers = new[]{ "206-555-0101", "425-882-8080" }},
new {Name = "Bob Harris",PhoneNumbers = new[] { "650-555-0199" }}};
IEnumerable<string> name = contacts.MySelect(j => j.Name);
在这里MySelect是一个扩展函数,所以contacts.MySelect(j => j.Name)运行时将翻译成Extensions.MySelect(contacts,j => j.Name),在这里T就是包含Name和电话号码的匿名类型,所以S就是string 类型。
6.4 如果存在两个相同名称的带有Lambda表达式为参数的方法,在隐式转换过程中输入参数和返回值的类型以最容易转换为原则进行匹配,如:
{
public int Sum<T>(Func<T, int> selector)
{
int sum = 0;
foreach (T item in this) sum += selector(item);
return sum;
}
public double Sum<T>(Func<T, double> selector)
{
double sum = 0;
foreach (T item in this) sum += selector(item);
return sum;
}
}
class Detail
{
public int UnitCount;
public double UnitPrice;
}
void ComputeSums()
{
ItemList<Detail> orderDetails = GetOrderDetails();
int totalUnits = orderDetails.Sum(d => d.UnitCount);
double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
}
7 Partial方法:
在partial类中你可以声明partial方法(就是方法前加了partial关键的的方法),主要是用于将方法的定义和签名分开。这就为我们的类设计人员提供了类似于事件处理的方法钩子,开发人员决定是否实现他,如果方法只提供了方法签名,而没有实现方法部分的定义,那么编译器会在编译时移除掉该方法的签名(包括调用该方法签名的地方),不过partial方法必须满足以下条件:
1 该方法必须返回void.
2 该方法没有方法修饰符和Attribute应用。
3 定义的方法和签名必须完全匹配。
{
partial class A
{
partial void OnSomethingHappened(string s);
}
// This part can be in a separate file.
partial class A
{ // Comment out this method and the program will still compile.
partial void OnSomethingHappened(String s)
{
Console.WriteLine("Something happened: {0}", s);
}
}
}
8 自动属性
在我们定义类属性时,特别是在定义值对象属性时,通常该属性对应的字段没有其他用途,在这种情况下我们可以通过一种简写的方式来实现:
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}
这样编译器会自动生成private的字段,然后完成get和set的定义。
9 查询表达式
将在以后补上