学习和使用.NET平台已有六七个年头了,05年开始学习.NET的时候,当时网上的资源不像现在这样丰富,所以去电脑城买了张盗版的VS2005的光盘,安装时才发现是VS2003,当时有一种被坑的感觉,但也正是如此,让我有了一个完整的.NET的学习生涯。

从我从业的经历来看VS2003和VS2008是使用的比较多的,一些历史遗留的老项目由种种原因没有升级,依然在使用VS2003。我现在所在的公司也只使用VS2003和VS2008这两种开发工具,尽管TFS已经是2010了。很多时候公司觉得够用也就不去升级开发工具了。从1.0到4.0的变化主要体现在DotNet平台、语言和开发工具这三个方面。本文只是概括性的讲述在每个版本里都有些什么新的特性。先看下面的一张表格,描述的不同版本的出生年份、对应的VS版本和CLR的版本。

上图中需要注意的一点是:2.0 3.0 3.5的CLR版本都是2.0。


在1.1版本中,从语言的角度就是基本的面向对象的语法,可以说任何一般C#语言的书籍都包含了C#1的所有内容。工具方面VS2003已经很强大了,即便是到了现在的VS2012,我们80%时候用到的也只不过是20%的功能。

在VS2003中创建ASP.NET项目默认是跟IIS相关联的,从VSS中拉VS2003的代码时需要先在IIS中创建好站点,端口要和VSS中代码的相对应,但有时会出现很多问题。我一般的做法是将代码拉到本地后,修改解决方案文件中的路径为本地路径,然后修改项目文件中的ProjectType为Local,默认是Web,就可以很顺利打开项目了。


2.0中对应VS2005我用的也不多,因为很快就被VS2008替代了,不过在语言方面却带来了很多新的东西。

★C#2中最重要的一个特性应该就是泛型。泛型的用处就是在一些场景下可以减少强制转换来提高性能。在C#1中就有很多的强制转换,特别是对一些集合进行遍历时,如ArrayList、HashTable,因为他们是为不同数据类型设计的集合,所以他们中键和值的类型都是object,这就意味着会平凡发生装箱拆箱的才做。C#2中有了泛型,所以我们可以使用List<T>、Dictionary<Tkey,TValue> 。泛型能够带来很好的编译时类型检查,也不会有装箱拆箱的才做,因为类型是在使用泛型的时候就已经指定了。

DotNet已经通过了很多的泛型类型供我们使用,如上面提到的List<T>,Dictionary<Tkey,TValue>,我们也可以自己来创建泛型类型(类、接口、委托、结构)或是方法。在定义泛型类型或时可以通过定义泛型约束来对泛型参数进行限制,更好的使用编译时检查。泛型约束是通过关键字where来实现的,C#2中的泛型约束有4种:

  • 引用类型约束:确保类型实参是引用类型,使用where T:class来表示;
  • 值类型约束:确保类型实参是值类型,使用where T:truct来表示;
  • 构造函数类型约束,使用where T:new()来表示;
  • 转换类型约束:约束类型实参是另外的一种类型,例如:where T:IDisposable 。

★C#2提供了分部类(Partil)。分部类可以允许我们在多个文件中为一个类型编写代码,在Asp.Net2.0中用的极为广泛。新建一个Aspx页面,页面的CodeBehind和页面中的控件的定义就是通过分部类来实现的。如下:

 
  1. public partial class _Default : System.Web.UI.Page   
  2. public partial class _Default  

分布类使用关键字partial来定义,当一个类中的代码非常多时,可以使用分部类来进行拆分,这对代码的阅读很有好处,而且不会影响调用。

★C#2中增加了静态类,静态类中的公用方法必须也是静态的,可以由类名直接调用,不需要实例化,比较适用于编写一些工具类。如System.Math类就是静态类。工具类有一些特点,如:所有成员都是静态的、不需要被继承、不需要进行实例化。在C#1中我们可以通过如下代码来实现:

 
  1. //声明为密封类防止被继承   
  2. public sealed class StringHelper  
  3. {  
  4.     //添加私有无参构造函ˉ数防止被实例化,如果不添加私有构造函数 //会自动生成共有无参构造函数   
  5.     private StringHelper(){};  
  6.     public static int StringToInt32(string input)  
  7.     {  
  8.         int result=0;  
  9.         Int32.TryParse(input, out result);  
  10.         return result;  
  11.     }  
 
  1. C#2中可以使用静态类来实现:  
  2.  
  3. public static class StringHelper  
  4. {  
  5.     public static int StringToInt32(string input)    {        int result=0;  
  6.         Int32.TryParse(input, out result);  
  7.         return result;  
  8.     }  
  9. }  

★在C#1中声明属性,属性中的get和set的访问级别是和属性一致,要么都是public要么都是private,如果要实现get和set有不同的访问级别,则需要用一种变通的方式,自己写GetXXX和SetXXX方法。在C#2中可以单独设置get和set的访问级别,如下:

 
  1. private string name;  
  2. public string Name  
  3. {  
  4.     get { return name; }  
  5.     private set { name = value; }  

需要注意的是,不能讲属性设置为私有的,而将其中的get或是set设置成公有的,也不能给set和get设置相同的访问级别,当set和get的访问级别相同时,我们可以直接设置在属性上。

★命名空间可以用来组织类,当不同的命名空间中有相同的类时,可以使用完全限定名来防止类名的冲突,C#1中可以使用空间别名来简化书写,空间别名用using关键字实现,看下面一个例子:

 
  1. namespace NameSpaceDemo1  
  2. {  
  3.     public class ClassDemo{}  
  4. }  
  5. namespace NameSpaceDemo2  
  6. {  
  7.     public class ClassDemo{}  
 
  1. using Demo1 = NameSpaceDemo1;  
  2. using Demo2 = NameSpaceDemo2;  
  3. namespace WebApplication2  
  4. {  
  5.     public partial class _Default : System.Web.UI.Page   
  6.     {  
  7.         protected void Page_Load(object sender, EventArgs e)  
  8.         {  
  9.             Demo1.ClassDemo c1 = new Demo1.ClassDemo();  
  10.             Demo2.ClassDemo c2 = new Demo2.ClassDemo();  
  11.         }  
  12.     }  

如果我们在使用中添加了名为Demo1或是Demo2的类,那么空间别名的使用就会受到影响,在C#1中就得使用完全限定名了。在C#2中可以用“::”符号来解决,如下面代码:

 
  1. namespace WebApplication2  
  2. {  
  3.     class Demo1 { }  
  4.     public partial class _Default : System.Web.UI.Page   
  5.     {  
  6.         protected void Page_Load(object sender, EventArgs e)  
  7.         {  
  8.             Demo1::ClassDemo c1 = new Demo1::ClassDemo();  
  9.             Demo2.ClassDemo c2 = new Demo2.ClassDemo();  
  10.         }  
  11.     }  

如果有两个类,一个在包含在命名空间中,一个没有包含在命名空间中,那么要访问没有包含在命名空间中的类时,在C#2中可以使用“global::”,代码如下:

 
  1. public class ClassDemo{}  
  2. namespace NameSpaceDemo1  
  3. {  
  4.     public class ClassDemo{}  
  5.     public class Test   
  6.     {  
  7.         static void Main()  
  8.         {  
  9.             Console.WriteLine(typeof(ClassDemo)); //NameSpaceDemo1.ClassDemo   
  10.             Console.WriteLine(typeof(global::ClassDemo)); //ClassDemo   
  11.             Console.ReadLine();  
  12.         }  
  13.     }  

还有一种命名空间别名叫外部别名,用来处理不同的dll中存在相同的命名空间和类型即完全限定名相同,假设Firs.dll和Second.dll中都有完全限定名为“Demo.Class1″的类型,这时使用完全限定名也无法区分,就必须使用外部别名了,代码如下:

 
  1. extern alias FirstDemo;  
  2. extern alias SecondDemo;  
  3. using System;  
  4. namespace NameSpaceDemo1  
  5. {  
  6.     public class ClassDemo{}  
  7.     public class Test   
  8.     {  
  9.         static void Main()  
  10.         {  
  11.             Console.WriteLine(typeof(FirstDemo::Demo.Class1));  
  12.             Console.WriteLine(typeof(SecondDemo::Demo.Class1));  
  13.             Console.ReadLine();  
  14.         }  
  15.     }  

使用外部别名有两点需要注意:

1 extern alias必须声明在最上方;

2 引用了第三方dll,如First.dll和Second.dll,需要修改引入dll的Aliasces属性值,像上面的例子中分别是FirstDemo和SecondDemo,在引入的dll上点击右键,选择属性,如下:

★C#2中还提供了友元程序集,具体参考我以前博文《》

★C#2中提供了可空类型,说直白点就是允许值类型的值为null。正常来说值类型的值是不应该为null的,但我们的很多应用是和数据库打交道的,而数据库中的类型都是可以为null值的,这就造成了我们写程序的时候有时需要将值类型设置为null。在C#1中通常使用”魔值“来处理这种情况,比如DateTiem.MinValue、Int32.MinValue。在ADO.NET中所有类型的空值可以用DBNull.Value来表示。C#2中可空类型主要是使用System.Nullable<T>的泛型类型,类型参数T有值类型约束。可以像下面这样来定义可空类型:

 
  1. Nullable<int> i = 20;  
  2. Nullable<bool> b = true

C#2中也提供了更方便的定义方式,使用操作符?:

 
  1. int? i = 20;  
  2. bool? b = true

★C#2中对迭代器提供了更便捷的实现方式。提到迭代器,有两个概念需要了解,可枚举对象和枚举器,实现了System.Collections.IEnumerable接口的对象是可枚举对象,这些对象可以被C#中的foreach进行迭代;实现了System.Collections.IEnumeror接口的对象被称为枚举器。在C#1中实现迭代器非常繁琐,看下面一个例子:

 
  1. public class Test   
  2. {  
  3.     static void Main()  
  4.     {  
  5.         Person arrPerson = new Person("oec2003","oec2004","oec2005");  
  6.         foreach (string p in arrPerson)  
  7.         {  
  8.             Console.WriteLine(p);  
  9.         }  
  10.         Console.ReadLine();  
  11.     }  
  12. }  
  13. public class Person:IEnumerable   
  14. {  
  15.     public Person(params string[] names)  
  16.     {  
  17.         _names = new string[names.Length];  
  18.         names.CopyTo(_names, 0);  
  19.     }  
  20.     public string[] _names;  
  21.     public IEnumerator GetEnumerator()  
  22.     {  
  23.         return new PersonEnumerator(this);  
  24.     }  
  25.     private string this[int index]  
  26.     {  
  27.         get { return _names[index]; }  
  28.         set { _names[index] = value; }  
  29.     }  
  30. }  
  31. public class PersonEnumerator : IEnumerator   
  32. {  
  33.     private int _index = -1;  
  34.     private Person _p;  
  35.     public PersonEnumerator(Person p) { _p = p; }  
  36.     public object Current  
  37.     {  
  38.         get { return _p._names[_index]; }  
  39.     }  
  40.     public bool MoveNext()  
  41.     {  
  42.         _index++;  
  43.         return _index < _p._names.Length;  
  44.     }  
  45.     public void Reset()  
  46.     {  
  47.         _index = -1;  
  48.     }  

C#2中的迭代器变得非常便捷,使用关键字yield return关键字实现,下面是C#2中使用yield return的重写版本:

 
  1. public class Test   
  2. {  
  3.     static void Main()  
  4.     {  
  5.         Person arrPerson = new Person("oec2003","oec2004","oec2005");  
  6.         foreach (string p in arrPerson)  
  7.         {  
  8.             Console.WriteLine(p);  
  9.         }  
  10.         Console.ReadLine();  
  11.     }  
  12. }  
  13. public class Person:IEnumerable   
  14. {  
  15.     public Person(params string[] names)  
  16.     {  
  17.         _names = new string[names.Length];  
  18.         names.CopyTo(_names, 0);  
  19.     }  
  20.     public string[] _names;  
  21.     public IEnumerator GetEnumerator()  
  22.     {  
  23.         foreach (string s in _names)  
  24.         {  
  25.             yield return s;  
  26.         }  
  27.     }  

★C#2中提供了匿名方法,匿名方法比较适用于定义必须通过委托调用的方法,用多线程来举个例子,在C#1中代码如下:

 
  1. private void btnTest_Click(object sender, EventArgs e)  
  2. {  
  3.     Thread thread = new Thread(new ThreadStart(DoWork));  
  4.     thread.Start();  
  5. }  
  6. private void DoWork()  
  7. {  
  8.     for (int i = 0; i < 100; i++)  
  9.     {  
  10.         Thread.Sleep(100);  
  11.         this.Invoke(new Action<string>(this.ChangeLabel),i.ToString());  
  12.     }  
  13. }  
  14. private void ChangeLabel(string i)  
  15. {  
  16.     label1.Text = i + "/100";  

使用C#2中的匿名方法,上面的例子中可以省去DoWork和ChangeLabel两个方法,代码如下:

 
  1. private void btnTest_Click(object sender, EventArgs e)  
  2. {  
  3.     Thread thread = new Thread(new ThreadStart(delegate() {  
  4.         for (int i = 0; i < 100; i++)  
  5.         {  
  6.             Thread.Sleep(100);  
  7.             this.Invoke(new Action(delegate() { label1.Text = i + "/100"; }));  
  8.         }  
  9.     }));  
  10.     thread.Start();  

★C#2对数据类型的转换提供了TryParse,可以更好的进行容错,看下面代码:

 
  1. static void Main()  
  2. {  
  3.     int a = 0;  
  4.     string b = "30"c = "aaa";  
  5.     int.TryParse(b, out a);  
  6.     Console.WriteLine(a);//30 int.TryParse(c, out a);  
  7.     Console.WriteLine(a);//0 Console.ReadLine();  

如果说C#2中的核心是泛型的话,那么C#3中的核心就应是Linq了,C#3中的特性几乎都是为Linq服务的,但每一项特性都可以脱离Linq来使用。下面就来看下C#3中有哪些特性。

★自动实现的属性,这个特性非常简单,就是使定义属性变得更简单了。代码如下:

 
  1. public string Name { get; set; }  
  2. public int Age { private set; get; } 

在VS中输入prop,并且按tab键两次,就可以快速的添加属性。

★隐式类型的局部变量,该特性是让我们在定义变量时可以比较动态化,使用var关键字作为类型的占位符,然后由编译器来推导变量的类型。具体参见《》。隐式类型虽然让编码方便了,但有些不少限制:

  • 被声明的变量只能是局部变量,而不能是静态变量和实例字段;
  • 变量在声明的同时必须初始化,初始化值不能为null;
  • 语句中只能声明一个变量;

★对象集合初始化器,简化了对象和集合的创建,具体参见《》。

★隐式类型的数组,和隐式类型的局部变量类似,可以不用显示指定类型来进行数组的定义,通常我们定义数组是这样:

 
  1. string[] names = { "oec2003", "oec2004", "oec2005" }; 

使用匿名类型数组可以想下面这样定义:

 
  1. protected void Page_Load(object sender, EventArgs e)  
  2. {  
  3.     GetName(new[] { "oec2003", "oec2004", "oec2005" });  
  4. }  
  5. public string GetName(string[] names)  
  6. {  
  7.     return names[0];  

★匿名类型 ,具体参见《》。

★扩展方法,可以在现有的类型上添加一些自定义的方法,比如可以在string类型上添加一个扩展方法ToInt32,就可以像“20”.ToInt32()这样调用了。具体参见《》。

★Lambda表达式,实际上是一个匿名方法,Lambda表达的表现形式是:(参数列表)=>{语句},看一个例子,创建一个委托实例,获取一个string类型的字符串,并返回字符串的长度。代码如下:

 
  1. Func<string, int> func = delegate(string s) { return s.Length; };  
  2. Console.WriteLine(func("oec2003")); 

使用Lambda的写法如下:

 
  1. Func<string, int> func = (string s)=> { return s.Length; };  
  2. Func<string, int> func1 = (s) => { return s.Length; };  
  3. Func<string, int> func2 = s => s.Length; 

上面三种写法是逐步简化的过程。

★Lambda表达式树,是.NET3.5中提出的一种表达方式,提供一种抽象的方式将一些代码表示成一个对象树。要使用Lambda表达式树需要引用命名空间System.Linq.Expressions,下面代码构建一个1+2的表达式树,最终表达式树编译成委托来得到执行结果:

 
  1. Expression a = Expression.Constant(1);  
  2. Expression b = Expression.Constant(2);  
  3. Expression add = Expression.Add(a, b);  
  4. Console.WriteLine(add); //(1+2) Func<int> fAdd = Expression.Lambda<Func<int>>(add).Compile();  
  5. Console.WriteLine(fAdd()); //3  

Lambda和Lambda表达式树为我们使用Linq提供了很多支持,如果我们在做的一个管理系统使用了Linq To Sql,在列表页会有按多个条件来进行数据的筛选的功能,这时就可以使用Lambda表达式树来进行封装查询条件,下面的类封装了And和Or两种条件:

 
  1. public static class DynamicLinqExpressions   
  2. {  
  3.     public static Expression<Func<T, bool>> True<T>() { return f => true; }  
  4.     public static Expression<Func<T, bool>> False<T>() { return f => false; }  
  5.  
  6.     public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,  
  7.                                                         Expression<Func<T, bool>> expr2)  
  8.     {  
  9.         var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());  
  10.         return Expression.Lambda<Func<T, bool>> 
  11.               (Expression.Or(expr1.Body, invokedExpr), expr1.Parameters);  
  12.     }  
  13.  
  14.     public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,  
  15.                                                          Expression<Func<T, bool>> expr2)  
  16.     {  
  17.         var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());  
  18.         return Expression.Lambda<Func<T, bool>> 
  19.               (Expression.And(expr1.Body, invokedExpr), expr1.Parameters);  
  20.     }  

下面是获取条件的方法:

 
  1. public Expression<Func<Courses, bool>> GetCondition()  
  2. {  
  3.     var exp = DynamicLinqExpressions.True<Courses>();  
  4.     if (txtCourseName.Text.Trim().Length > 0)  
  5.     {  
  6.         expexp = exp.And(g => g.CourseName.Contains(txtCourseName.Text.Trim()));  
  7.     }  
  8.     if (ddlGrade.SelectedValue != "-1")  
  9.     {  
  10.         expexp=exp.And(g => g.GradeID.Equals(ddlGrade.SelectedValue));  
  11.     }  
  12.     return exp;  

★Linq是一个很大的话题,也是NET3.5中比较核心的内容,有很多书籍专门来介绍Linq,下面只是做一些简单的介绍,需要注意的是Linq并非是Linq To Sql,Linq是一个大的集合,里面包含:

  • Linq To Object:提供对集合和对象的处理;
  • Linq To XML:应用于XML;
  • Linq To Sql:应用于SqlServer数据库;
  • Linq To DataSet: DataSet;
  • Linq To Entities:应用于SqlServer之外的关系数据库,我们还可以通过Linq的扩展框架来实现更多支持Linq的数据源。

下面以Linq To Object为例子来看看Linq是怎么使用的:

 
  1. public class UserInfo   
  2. {  
  3.     public string Name { get; set; }  
  4.     public int Age { get; set; }  
  5. }  
  6. public class Test   
  7. {  
  8.     static void Main()  
  9.     {  
  10.         List<UserInfo> users = new List<UserInfo>()  
  11.         {  
  12.             new UserInfo{
    Name="oec2003",Age=20},  
  13.             new UserInfo{
    Name="oec2004",Age=21},  
  14.             new UserInfo{
    Name="oec2005",Age=22}  
  15.         };  
  16.         IEnumerable<UserInfo> selectedUser = from user in users  
  17.                                              where user.Age > 20  
  18.                                              orderby user.Age descending select user;  
  19.         foreach (UserInfo user in selectedUser)  
  20.         {  
  21.             Console.WriteLine("姓名:"+user.Name+",年龄:"+user.Age);  
  22.         }  
  23.         Console.ReadLine();  
  24.     }  

可以看出,Linq可以让我们使用类似Sql的关键字来对集合、对象、XML等进行查询。

到这儿,C#3&NET3的一些特性也就说的差不多了,下面就看看C#4&NET4又有哪些变化吧。


★NET4随着VS2010一起发布,VS2010相比较VS2008有了很大的变化,有关VS2010的功能改进方面可以看我以前的博文:

★NET4 FramWork也有一些更新,比如提供了BigInteger、Complex、Tuple等,具体参见《》。

★在ASP.NET方面也有不少方便实用的特性,可以参考以下博文:

在C#方面,比较典型的两个特性就是可选参数和命名实参,还有一个大的特性–动态类型。

★VB在很早就已经支持了可选参数,而C#知道4了才支持,顾名思义,可选参数就是一些参数可以是可选的,在方法调用的时候可以不用输入。看下面代码:

 
  1. public class Test   
  2. {  
  3.     static void Main()  
  4.     {  
  5.         Console.WriteLine(GetUserInfo()); //姓名:ooec2003,年龄:30   
  6.         Console.WriteLine(GetUserInfo("oec2004", 20));//姓名:ooec2004,年龄:20   
  7.         Console.ReadLine();  
  8.     }  
  9.     public static string GetUserInfo(string name = "oec2003", int age = 30)  
  10.     {  
  11.         return "姓名:" + name + ",年龄:" + age.ToString();  
  12.     }  

★命名实参是在制定实参的值时,可以同时指定相应参数的名称。编译器可以判断参数的名称是否正确,命名实参可以让我们在调用时改变参数的顺序。命名实参也经常和可选参数一起使用,看下面的代码:

 
  1. static void Main()  
  2. {  
  3.     Console.WriteLine(Cal());//9   
  4.     Console.WriteLine(Cal(z: 5, y: 4));//25   
  5.     Console.ReadLine();  
  6. }  
  7. public static int Cal(int x=1, int y=2, int z=3)  
  8. {  
  9.     return (x + y) * z;  

★动态类型,C#使用dynamic来实现动态类型,在没用使用dynamic的地方,C#依然是静态的。静态类型中当我们要使用程序集中的类,要调用类中的方法,编译器必须知道程序集中有这个类,类里有这个方法,如果不能事先知道,编译时会报错,在C#4以前可以通过反射来解决这个问题。看一个使用dynamic的小例子:

 
  1. dynamic a = "oec2003";  
  2. Console.WriteLine(a.Length);//7   
  3. Console.WriteLine(a.length);//string 类型不包含length属性,但编译不会报错,运行时会报错   
  4. Console.ReadLine(); 

有人可能会发现使用dynamic声明变量和C#3中提供的var有点类似,其他他们是有本质区别的,var声明的变量在编译时会去推断出实际的类型,var只是相当于一个占位符,而dynamic声明的变量在编译时不会进行类型检查。

dynamic用的比较多的应该是替代以前的反射,而且性能有很大提高。假设有一个名为DynamicLib的程序集中有一个DynamicClassDemo类,类中有一个Cal方法,下面看看利用反射怎么访问Cal方法:

 
  1. namespace DynamicLib  
  2. {  
  3.     public class DynamicClassDemo   
  4.     {  
  5.         public int Cal(int x = 1, int y = 2, int z = 3)  
  6.         {  
  7.             return (x + y) * z;  
  8.         }  
  9.     }  
  10. }static void Main()  
  11. {  
  12.     Assembly assembly = Assembly.Load("DynamicLib");  
  13.     object obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo");  
  14.     Type type = obj.GetType();  
  15.     MethodInfo method = type.GetMethod("Cal");  
  16.     Console.WriteLine(method.Invoke(obj, new object[] { 1, 2, 3 }));//9   
  17.     Console.ReadLine();  

用dynamic的代码如下:

 
  1. Assembly assembly = Assembly.Load("DynamicLib");  
  2. dynamic obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo");  
  3. Console.WriteLine(obj.Cal());  
  4. Console.ReadLine(); 

★C#4中还有一些COM互操作性的改进和逆变性和协变性的改进,我几乎没有用到,所以在此就不讲述了。


至此,所以内容已经完成,算是我对C#&NET1.0-4.0知识的一个梳理, 由于本人能力有限,本文只是我所涉及到的一小部分,随着我以后的学习,我会再开篇来记录新的学习体会。希望本文对你有所帮助。