一. 隐式类型变量 (Implicitly typed local variables)
这个特性应该是C# 3.0 中最Simple的特性了, 它能让你使用一个新的关键字 “var” 来声明变量(不能用来声明字段), 例如:
var i = 5;
var str = “Csharp”;
var list = new List<int>();
而我们的编译器做的事情只是吧那个var换成了对应的类型, 上面的代码和下面的完全等价:
int i = 5;
string str = “Csharp”;
List<int> list = new List<int>();
所以, 使用var声明变量必须带有变量的初始化语句, 因为这样编译器才能知道变量的类型, 当然更不能用null初始化.. 编译器并不知道这个null是什么类型的null, C# 3.0 虽然带来了一些动态的特性, 但C#始终是个强类型的语言.
再说两句:
如果在你的程序中有一个叫var的类型, 那你就不能再用这个关键字了.. 所以… 呃, 你知道的..
这个语法产生的便利是否真的有价值呢? Of course! C#团队都做出来了… 可是当你去查看一个用var声明的变量的类型时, 却要向变量名称的右边看, 会不会有点不爽呢?
二. 扩展方法 (Extension methods)
哦耶, 又是一个语法糖, 不过这个特性比上一个的var似乎要更甜一些, 它甚至能让你在没有源代码的情况下给类添加方法! 看看以下的调用:
int num = int.Parse(textBox.Text);
上面那句代码你肯定已经写过N遍了, 你知道任何东西都有一个ToString方法, 但你肯定没想到今天我们的string也有了ToInt方法! 就像下面写的:
int num = textBox.Text.ToInt();
要办到它, 在C# 3.0里很简单, 你只需要在一个static类里写这么一个方法便行了:
static class MyExtensions
{
public static int ToInt(this string str)
{
return int.Parse(str);
}
}
然后在最新的Visual Studio Orcas beta 1里, 随便在一个string对象后面输入一个点, 你就会发现这个ToInt方法了.
编辑器怎么知道这个ToInt是string的扩展方法呢?
1. 这个方法是static的
2. 这个方法写在一个static类里
3. 这个方法的第一参数类型是扩展方法所扩展的类型, 且在参数前有关键字this
当然扩展方法不是只能有一个参数的, 更多的参数声明在this参数之后就行了, 例如:
static class MyExtensions
{
public static int ToInt(this string str, int time)
{
return int.Parse(str) * time;
}
}
于是可以这样调用它:
int two = “1”.ToInt(2);
扩展方法是怎么实现的呢? 其实很简单, 比如上面那句的调用在编译器的眼里和下面一句是完全等同的:
int two = MyExtensions.ToInt(“1”, 2);
显然, 我们还是没有改变string类, 当然也就不能像string本身的方法一样访问它的私有信息了 :(
虽然扩展方法也不是太高深, 但它在C# 3.0的编程实践中还是有很多体现的, 比如自带的一些对于可迭代类型的扩展方法, 都是很实用的:
三. Lambda 表达式 (Lambda expression)
先看一段有Lambda表达式的代码:
static void Main(string[] args)
{
int[] lotNums = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var evens = lotNums.Where(n => n % 2 == 0);
foreach (var e in evens)
{
Console.WriteLine(e);
}
}
这些代码的功能是: 从一堆数字中选出偶数并输出.
想想看如果要用C# 2.0的语法我们应该怎么做? 定义一个List<int>, 迭代整个数组, 如果这个数字对2取余等于0就扔到那个List里. 这些代码起码要写N行(N >= 1),是吧?
但C# 3.0的语法把却真正把上面的操作浓缩成了一行代码:
var evens = lotNums.Where(n => n % 2 == 0);
lotNums这个数组调用了一个叫Where的扩展方法, 这个扩展方法是 .NET Framework 3.5的BCL中自带的, 看看它的声明:
public static IEnumerable<TSource> Where<TSource>
(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
是一个泛型方法, 可是上面的调用并没有写<int>啊, 这是因为在C# 3.0的编译器比2.0的聪明很多, 既然你传进来的是IEnumerable<int>, 我调用的当然是Where<int>了..这种语法特性可以让你少写很多冗余的东西.
Where方法是传进来两个参数, 一个是原来的集合, 另一个是筛选偶数用的一个delegate, 也就是Func, 这里使用的两个泛型参数的Func委托类型的声明是:
public delegate TResult Func<TArg0, TResult>(TArg0 arg0);
Where 的第二个参数类型Func<TSource, bool> 指的是这样一个委托: 传入TSource对象, 返回一个bool值, 显然程序中的那句Lambda表达式n => n % 2 == 0就对应了Where的第二个参数predicate. 于是我们可以猜想 n => n % 2 == 0 的前一个n就是Func<int, bool>委托的int类型的传入的参数, 而返回的bool值就是n % 2 == 0.
其实把 var evens = lotNums.Where(n => n % 2 == 0) 改写成C# 2.0的语法能更容易理解Lambda语法的使用:
IEnumerable<int> evens = Enumerable.Where(lotNums,
delegate(int n) { return n % 2 == 0; } );
噢. 原来Lambda表达式就是匿名方法...
是的, 准确的说是可以少写一些代码的匿名方法.
以上所用到的Lambda表达式是最简单的写法, 还有更复杂的Lambda表达式的写法,比如可以设置多个参数, =>操作符后可以有大括号扩起的一堆语句, 你可以参考C# 3.0规范或者其他文档, 总之, 匿名方法能表达的, Lambda表达式都能表达.
四. 再说两句
C# 3.0 所有的特性都是编译器层面上的改进, 编译出的IL以及执行它的CLR都没有任何改变, 所有的语言新特性也只是越来越方便编码,让代码更好写更好读. C#看上去越来越美了……
C# 3.0 还有好几个特性以后再慢慢再说, 其实LINQ就是这所有基本特性的一个组合.