學習和使用.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和頁面中的控件的定義就是通過分部類來實現的。如下:
public partial class _Default : System.Web.UI.Page public partial class _Default
分佈類使用關鍵字partial來定義,當一個類中的代碼非常多時,可以使用分部類來進行拆分,這對代碼的閱讀很有好處,而且不會影響調用。
★C#2中增加了靜態類,靜態類中的公用方法必須也是靜態的,可以由類名直接調用,不需要實例化,比較適用於編寫一些工具類。如System.Math類就是靜態類。工具類有一些特點,如:所有成員都是靜態的、不需要被繼承、不需要進行實例化。在C#1中我們可以通過如下代碼來實現:
//聲明爲密封類防止被繼承 public sealed class StringHelper { //添加私有無參構造函ˉ數防止被實例化,如果不添加私有構造函數 //會自動生成共有無參構造函數 private StringHelper(){}; public static int StringToInt32(string input) { int result=0; Int32.TryParse(input, out result); return result; } }
C#2中可以使用靜態類來實現:
public static class StringHelper { public static int StringToInt32(string input) { int result=0; Int32.TryParse(input, out result); return result; } }
★在C#1中聲明屬性,屬性中的get和set的訪問級別是和屬性一致,要麼都是public要麼都是private,如果要實現get和set有不同的訪問級別,則需要用一種變通的方式,自己寫GetXXX和SetXXX方法。在C#2中可以單獨設置get和set的訪問級別,如下:
private string name; public string Name { get { return name; } private set { name = value; } }
需要注意的是,不能講屬性設置爲私有的,而將其中的get或是set設置成公有的,也不能給set和get設置相同的訪問級別,當set和get的訪問級別相同時,我們可以直接設置在屬性上。
★命名空間可以用來組織類,當不同的命名空間中有相同的類時,可以使用完全限定名來防止類名的衝突,C#1中可以使用空間別名來簡化書寫,空間別名用using關鍵字實現,看下面一個例子:
namespace NameSpaceDemo1 { public class ClassDemo{} } namespace NameSpaceDemo2 { public class ClassDemo{} }
using Demo1 = NameSpaceDemo1; using Demo2 = NameSpaceDemo2; namespace WebApplication2 { public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Demo1.ClassDemo c1 = new Demo1.ClassDemo(); Demo2.ClassDemo c2 = new Demo2.ClassDemo(); } } }
如果我們在使用中添加了名爲Demo1或是Demo2的類,那麼空間別名的使用就會受到影響,在C#1中就得使用完全限定名了。在C#2中可以用“::”符號來解決,如下面代碼:
namespace WebApplication2 { class Demo1 { } public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Demo1::ClassDemo c1 = new Demo1::ClassDemo(); Demo2.ClassDemo c2 = new Demo2.ClassDemo(); } } }
如果有兩個類,一個在包含在命名空間中,一個沒有包含在命名空間中,那麼要訪問沒有包含在命名空間中的類時,在C#2中可以使用“global::”,代碼如下:
public class ClassDemo{} namespace NameSpaceDemo1 { public class ClassDemo{} public class Test { static void Main() { Console.WriteLine(typeof(ClassDemo)); //NameSpaceDemo1.ClassDemo Console.WriteLine(typeof(global::ClassDemo)); //ClassDemo Console.ReadLine(); } } }
還有一種命名空間別名叫外部別名,用來處理不同的dll中存在相同的命名空間和類型即完全限定名相同,假設Firs.dll和Second.dll中都有完全限定名爲“Demo.Class1"的類型,這時使用完全限定名也無法區分,就必須使用外部別名了,代碼如下:
extern alias FirstDemo; extern alias SecondDemo; using System; namespace NameSpaceDemo1 { public class ClassDemo{} public class Test { static void Main() { Console.WriteLine(typeof(FirstDemo::Demo.Class1)); Console.WriteLine(typeof(SecondDemo::Demo.Class1)); Console.ReadLine(); } } }
使用外部別名有兩點需要注意:
1 extern alias必須聲明在最上方;
2 引用了第三方dll,如First.dll和Second.dll,需要修改引入dll的Aliasces屬性值,像上面的例子中分別是FirstDemo和SecondDemo,在引入的dll上點擊右鍵,選擇屬性,如下:
★C#2中還提供了友元程序集,具體參考我以前博文《C#:友元程序集》
★C#2中提供了可空類型,說直白點就是允許值類型的值爲null。正常來說值類型的值是不應該爲null的,但我們的很多應用是和數據庫打交道的,而數據庫中的類型都是可以爲null值的,這就造成了我們寫程序的時候有時需要將值類型設置爲null。在C#1中通常使用”魔值“來處理這種情況,比如DateTiem.MinValue、Int32.MinValue。在ADO.NET中所有類型的空值可以用DBNull.Value來表示。C#2中可空類型主要是使用System.Nullable<T>的泛型類型,類型參數T有值類型約束。可以像下面這樣來定義可空類型:
Nullable<int> i = 20; Nullable<bool> b = true;
C#2中也提供了更方便的定義方式,使用操作符?:
int? i = 20; bool? b = true;
★C#2中對迭代器提供了更便捷的實現方式。提到迭代器,有兩個概念需要了解,可枚舉對象和枚舉器,實現了System.Collections.IEnumerable接口的對象是可枚舉對象,這些對象可以被C#中的foreach進行迭代;實現了System.Collections.IEnumeror接口的對象被稱爲枚舉器。在C#1中實現迭代器非常繁瑣,看下面一個例子:
public class Test { static void Main() { Person arrPerson = new Person("oec2003","oec2004","oec2005"); foreach (string p in arrPerson) { Console.WriteLine(p); } Console.ReadLine(); } } public class Person:IEnumerable { public Person(params string[] names) { _names = new string[names.Length]; names.CopyTo(_names, 0); } public string[] _names; public IEnumerator GetEnumerator() { return new PersonEnumerator(this); } private string this[int index] { get { return _names[index]; } set { _names[index] = value; } } } public class PersonEnumerator : IEnumerator { private int _index = -1; private Person _p; public PersonEnumerator(Person p) { _p = p; } public object Current { get { return _p._names[_index]; } } public bool MoveNext() { _index++; return _index < _p._names.Length; } public void Reset() { _index = -1; } }
C#2中的迭代器變得非常便捷,使用關鍵字yield return關鍵字實現,下面是C#2中使用yield return的重寫版本:
public class Test { static void Main() { Person arrPerson = new Person("oec2003","oec2004","oec2005"); foreach (string p in arrPerson) { Console.WriteLine(p); } Console.ReadLine(); } } public class Person:IEnumerable { public Person(params string[] names) { _names = new string[names.Length]; names.CopyTo(_names, 0); } public string[] _names; public IEnumerator GetEnumerator() { foreach (string s in _names) { yield return s; } } }
★C#2中提供了匿名方法,匿名方法比較適用於定義必須通過委託調用的方法,用多線程來舉個例子,在C#1中代碼如下:
private void btnTest_Click(object sender, EventArgs e) { Thread thread = new Thread(new ThreadStart(DoWork)); thread.Start(); } private void DoWork() { for (int i = 0; i < 100; i++) { Thread.Sleep(100); this.Invoke(new Action<string>(this.ChangeLabel),i.ToString()); } } private void ChangeLabel(string i) { label1.Text = i + "/100"; }
使用C#2中的匿名方法,上面的例子中可以省去DoWork和ChangeLabel兩個方法,代碼如下:
private void btnTest_Click(object sender, EventArgs e) { Thread thread = new Thread(new ThreadStart(delegate() { for (int i = 0; i < 100; i++) { Thread.Sleep(100); this.Invoke(new Action(delegate() { label1.Text = i + "/100"; })); } })); thread.Start(); }
★C#2對數據類型的轉換提供了TryParse,可以更好的進行容錯,看下面代碼:
static void Main() { int a = 0; string b = "30", c = "aaa"; int.TryParse(b, out a); Console.WriteLine(a);//30 int.TryParse(c, out a); Console.WriteLine(a);//0 Console.ReadLine(); }
如果說C#2中的核心是泛型的話,那麼C#3中的核心就應是Linq了,C#3中的特性幾乎都是爲Linq服務的,但每一項特性都可以脫離Linq來使用。下面就來看下C#3中有哪些特性。 ★自動實現的屬性,這個特性非常簡單,就是使定義屬性變得更簡單了。代碼如下:
public string Name { get; set; } public int Age { private set; get; }
在VS中輸入prop,並且按tab鍵兩次,就可以快速的添加屬性。
★隱式類型的局部變量,該特性是讓我們在定義變量時可以比較動態化,使用var關鍵字作爲類型的佔位符,然後由編譯器來推導變量的類型。具體參見《C#3.0學習(1)---隱含類型局部變量和擴展方法》。隱式類型雖然讓編碼方便了,但有些不少限制:
- 被聲明的變量只能是局部變量,而不能是靜態變量和實例字段;
- 變量在聲明的同時必須初始化,初始化值不能爲null;
- 語句中只能聲明一個變量;
★對象集合初始化器,簡化了對象和集合的創建,具體參見《C#3.0學習(2)---對象集合初始化器》。
★隱式類型的數組,和隱式類型的局部變量類似,可以不用顯示指定類型來進行數組的定義,通常我們定義數組是這樣:
string[] names = { "oec2003", "oec2004", "oec2005" };
使用匿名類型數組可以想下面這樣定義:
protected void Page_Load(object sender, EventArgs e) { GetName(new[] { "oec2003", "oec2004", "oec2005" }); } public string GetName(string[] names) { return names[0]; }
★匿名類型 ,具體參見《C#3.0學習(3)—匿名類型》。
★擴展方法,可以在現有的類型上添加一些自定義的方法,比如可以在string類型上添加一個擴展方法ToInt32,就可以像“20”.ToInt32()這樣調用了。具體參見《C#3.0學習(1)---隱含類型局部變量和擴展方法》。
★Lambda表達式,實際上是一個匿名方法,Lambda表達的表現形式是:(參數列表)=>{語句},看一個例子,創建一個委託實例,獲取一個string類型的字符串,並返回字符串的長度。代碼如下:
Func<string, int> func = delegate(string s) { return s.Length; }; Console.WriteLine(func("oec2003"));
使用Lambda的寫法如下:
Func<string, int> func = (string s)=> { return s.Length; }; Func<string, int> func1 = (s) => { return s.Length; }; Func<string, int> func2 = s => s.Length;
上面三種寫法是逐步簡化的過程。
★Lambda表達式樹,是.NET3.5中提出的一種表達方式,提供一種抽象的方式將一些代碼表示成一個對象樹。要使用Lambda表達式樹需要引用命名空間System.Linq.Expressions,下面代碼構建一個1+2的表達式樹,最終表達式樹編譯成委託來得到執行結果:
Expression a = Expression.Constant(1); Expression b = Expression.Constant(2); Expression add = Expression.Add(a, b); Console.WriteLine(add); //(1+2) Func<int> fAdd = Expression.Lambda<Func<int>>(add).Compile(); Console.WriteLine(fAdd()); //3
Lambda和Lambda表達式樹爲我們使用Linq提供了很多支持,如果我們在做的一個管理系統使用了Linq To Sql,在列表頁會有按多個條件來進行數據的篩選的功能,這時就可以使用Lambda表達式樹來進行封裝查詢條件,下面的類封裝了And和Or兩種條件:
public static class DynamicLinqExpressions { public static Expression<Func<T, bool>> True<T>() { return f => true; } public static Expression<Func<T, bool>> False<T>() { return f => false; } public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>()); return Expression.Lambda<Func<T, bool>> (Expression.Or(expr1.Body, invokedExpr), expr1.Parameters); } public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>()); return Expression.Lambda<Func<T, bool>> (Expression.And(expr1.Body, invokedExpr), expr1.Parameters); } }
下面是獲取條件的方法:
public Expression<Func<Courses, bool>> GetCondition() { var exp = DynamicLinqExpressions.True<Courses>(); if (txtCourseName.Text.Trim().Length > 0) { exp = exp.And(g => g.CourseName.Contains(txtCourseName.Text.Trim())); } if (ddlGrade.SelectedValue != "-1") { exp=exp.And(g => g.GradeID.Equals(ddlGrade.SelectedValue)); } 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是怎麼使用的:
public class UserInfo { public string Name { get; set; } public int Age { get; set; } } public class Test { static void Main() { List<UserInfo> users = new List<UserInfo>() { new UserInfo{Name="oec2003",Age=20}, new UserInfo{Name="oec2004",Age=21}, new UserInfo{Name="oec2005",Age=22} }; IEnumerable<UserInfo> selectedUser = from user in users where user.Age > 20 orderby user.Age descending select user; foreach (UserInfo user in selectedUser) { Console.WriteLine("姓名:"+user.Name+",年齡:"+user.Age); } Console.ReadLine(); } }
可以看出,Linq可以讓我們使用類似Sql的關鍵字來對集合、對象、XML等進行查詢。 到這兒,C#3&NET3的一些特性也就說的差不多了,下面就看看C#4&NET4又有哪些變化吧。
★NET4隨着VS2010一起發佈,VS2010相比較VS2008有了很大的變化,有關VS2010的功能改進方面可以看我以前的博文:
★NET4 FramWork也有一些更新,比如提供了BigInteger、Complex、Tuple等,具體參見《Net4.0---Framwork新增特性》。
★在ASP.NET方面也有不少方便實用的特性,可以參考以下博文:
- Net4.0---AspNet中URL重寫的改進
- Net4.0---AspNet中的控件HyperLink的改進(支持路由跳轉)
- Net4.0---對HTML淨化的處理
- Net4.0---數據綁定控件(GridView ListView…)中的ClientIDRowSuffix屬性
在C#方面,比較典型的兩個特性就是可選參數和命名實參,還有一個大的特性--動態類型。
★VB在很早就已經支持了可選參數,而C#知道4了才支持,顧名思義,可選參數就是一些參數可以是可選的,在方法調用的時候可以不用輸入。看下面代碼:
public class Test { static void Main() { Console.WriteLine(GetUserInfo()); //姓名:ooec2003,年齡:30 Console.WriteLine(GetUserInfo("oec2004", 20));//姓名:ooec2004,年齡:20 Console.ReadLine(); } public static string GetUserInfo(string name = "oec2003", int age = 30) { return "姓名:" + name + ",年齡:" + age.ToString(); } }
★命名實參是在制定實參的值時,可以同時指定相應參數的名稱。編譯器可以判斷參數的名稱是否正確,命名實參可以讓我們在調用時改變參數的順序。命名實參也經常和可選參數一起使用,看下面的代碼:
static void Main() { Console.WriteLine(Cal());//9 Console.WriteLine(Cal(z: 5, y: 4));//25 Console.ReadLine(); } public static int Cal(int x=1, int y=2, int z=3) { return (x + y) * z; }
★動態類型,C#使用dynamic來實現動態類型,在沒用使用dynamic的地方,C#依然是靜態的。靜態類型中當我們要使用程序集中的類,要調用類中的方法,編譯器必須知道程序集中有這個類,類裏有這個方法,如果不能事先知道,編譯時會報錯,在C#4以前可以通過反射來解決這個問題。看一個使用dynamic的小例子:
dynamic a = "oec2003"; Console.WriteLine(a.Length);//7 Console.WriteLine(a.length);//string 類型不包含length屬性,但編譯不會報錯,運行時會報錯 Console.ReadLine();
有人可能會發現使用dynamic聲明變量和C#3中提供的var有點類似,其他他們是有本質區別的,var聲明的變量在編譯時會去推斷出實際的類型,var只是相當於一個佔位符,而dynamic聲明的變量在編譯時不會進行類型檢查。 dynamic用的比較多的應該是替代以前的反射,而且性能有很大提高。假設有一個名爲DynamicLib的程序集中有一個DynamicClassDemo類,類中有一個Cal方法,下面看看利用反射怎麼訪問Cal方法:
namespace DynamicLib { public class DynamicClassDemo { public int Cal(int x = 1, int y = 2, int z = 3) { return (x + y) * z; } } }
static void Main() { Assembly assembly = Assembly.Load("DynamicLib"); object obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo"); Type type = obj.GetType(); MethodInfo method = type.GetMethod("Cal"); Console.WriteLine(method.Invoke(obj, new object[] { 1, 2, 3 }));//9 Console.ReadLine(); }
用dynamic的代碼如下:
Assembly assembly = Assembly.Load("DynamicLib"); dynamic obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo"); Console.WriteLine(obj.Cal()); Console.ReadLine();
★C#4中還有一些COM互操作性的改進和逆變性和協變性的改進,我幾乎沒有用到,所以在此就不講述了。
至此,所以內容已經完成,算是我對C#&NET1.0-4.0知識的一個梳理, 由於本人能力有限,本文只是我所涉及到的一小部分,隨着我以後的學習,我會再開篇來記錄新的學習體會。希望本文對你有所幫助。