C# & .NET 1.0-4.0

學習和使用.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方面也有不少方便實用的特性,可以參考以下博文:

在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知識的一個梳理, 由於本人能力有限,本文只是我所涉及到的一小部分,隨着我以後的學習,我會再開篇來記錄新的學習體會。希望本文對你有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章