1. 程式人生 > >《C#高階程式設計》讀書筆記-1

《C#高階程式設計》讀書筆記-1

《C#高階程式設計》讀書筆記

  1. C#型別的取值範圍

    名稱 CTS型別 說明 範圍
    sbyte System.SByte 8位有符號的整數 -128~127(−27−27~27−127−1)
    short System.Int16 16位有符號的整數 -32 768~32 767(−215−215~215−1215−1)
    int System.Int32 32位有符號的整數 -2 147 483 648~2 147 483 647(−231−231~231−1231−1)
    long System.Int64 64位有符號的整數 −263−263~263−1263−1
    byte System.Byte 8位無符號的整數 0~255(0~28−128−1)
    ushort System.UInt16 16位無符號的整數 0~65535(0~216−1216−1)
    uint System.UInt32 32位無符號的整數 0~4 294 967 295(0~232−1232−1)
    ulong System.UInt64 64位無符號的整數 0~18 446 744 073 709 551 615(0~264−1264−1)
  2. 訪問限制符

    修飾符 應用於 說明
    public 所有型別或成員 任何程式碼均可以訪問該項
    protected 型別和內嵌型別的所有成員 只有派生的型別能夠訪問該項
    internal 所有型別或成員 只能在包含它的程式集中訪問該項
    private 型別和內嵌型別的所有成員 只能在它所屬的型別中訪問該項
    protected internal 型別和內嵌型別的所有成員 只能在包含它的程式集和派生型別的任何程式碼中訪問該項
  3. C#常見的修飾符

    修飾符 應用於 說明
    new 函式成員 成員用相同的簽名隱藏繼承的成員
    static 所有成員 成員不作用於類的具體例項
    virtual 僅函式成員 成員可以由派生類重寫
    abstract 僅函式成員 虛擬成員定義了成員的簽名,但沒有提供實現程式碼
    override 僅函式成員 成員重寫了繼承的虛擬或抽象成員
    sealed 類、方法和屬性 對於類,不能繼承自密封類。對於屬性和方法,成員重寫已繼承的虛擬成員,但任何派生類中的任何成員都不能重寫該成員。該修飾符必須與override一起使用
    extern 僅靜態[DllImport]方法 成員在外部用另一種語言實現
  4. 結構體

    • 結構是值型別,不是引用型別。
    • 儲存在棧中或儲存為內聯(inline)(如果它們是儲存在堆中的另一個物件的一部分),其生存期的限制與簡單的資料型別一樣。
    • 結構體不支援繼承。
    • 對於結構建構函式的工作方式有一些區別。尤其是編譯器總是提供一個無引數的預設建構函式,它是不允許替換的。
    • 使用結構,可以指定欄位如何在記憶體中的佈局。
    • 注意,因為結構是值型別,所以new運算子與類和其他引用型別的工作方式不同。new運算子並不分配堆中的記憶體,而是隻呼叫相應的建構函式,根據傳送給它的引數初始化所有的欄位。
    • 結構遵循其他資料型別都遵循的規則:在使用前所有的元素都必須進行初始化。在結構上呼叫new運算子,或者給所有的欄位分別賦值,結構就完全初始化了。當然,如果結構定義為類的成員欄位,在初始化包含的物件時,該結構會自動初始化為0。
    • 結構是會影響效能的值型別,但根據使用結構的方式,這種影響可能是正面的,也可能是負面的。正面的影響是為結構分配記憶體時,速度非常快,因為它們將內聯或者儲存在棧中。在結構超出了作用域被刪除時,速度也很快。負面影響是,只要把結構作為引數來傳遞或者把一個結構賦予另一個結構(如A=B,其 中A和B是結構),結構的所有內容就被複制,而對於類,則只複製引用。這樣就會有效能損失,根據結構的大小,效能損失也不同。注意,結構主要用於小的資料結構。但當把結構作為引數傳遞給方法時,應把它作為ref引數傳遞,以避免效能損失————此時只傳遞了結構在記憶體中的地址,這樣傳遞速度就與在類中的傳遞速度一樣快了。但如果這樣做,就必須注意被呼叫的方法可以改變結構的值。
    • 結構不是為繼承設計的。這意味著:它不能從一個結構中繼承。唯一的例外是對應的結構(和C#中的其他型別一樣)最終派生於類System.Object。因此,結構也可以訪問System.Object的方法。在結構中,甚至可以重寫System.Object中的方法————如重寫ToString()方法。結構的繼承鏈是:每個結構派生自System.ValueType類,System.ValueType類又派生自System.ObjectValueType並沒有給Object新增任何新成員,但提供了一些更適合結構的實現方式。注意,不能為結構提供其他基類,每個結構都派生自ValueType
    • 為結構定義建構函式的方式與為類定義建構函式的方式相同,但 不允許定義無引數的建構函式。預設建構函式把數值欄位都初始化為0,把引用型別欄位初始化為null,且總是隱式地給出,即使提供了其他帶引數的建構函式,也是如此。提供欄位的初始值也不能繞過預設建構函式。
  5. 擴充套件方法

    • 擴充套件方法允許改變一個類,但不需要該類的原始碼。所以使用擴充套件方法的情景之一是,當不知道類的原始碼或者不想修改該類的原始碼卻想擴充套件該類,就可以用擴充套件方法。
    • 擴充套件方法是靜態方法,它是類的一部分,但實際上沒有放在類的原始碼中。
    • 擴充套件方法需放在靜態類中。
    • 對於擴充套件方法,第一個引數是要擴充套件的型別,它放在this關鍵字的後面。
    • 在擴充套件方法中,可以訪問所擴充套件型別的所有共有方法和屬性。
    • 如果擴充套件方法與類中的某個方法同名,就從來不會呼叫擴充套件方法。類中已有的任何例項方法優先。
  6. var關鍵字。編譯器可以根據變數的初始化值“推斷 ” 變數的型別。使用var關鍵字需要遵循的一些規則:

    • 變數必須初始化。否則,編譯器就沒有推斷變數型別的依據。
    • 初始化器不能為空。
    • 初始化器必須放在表示式中。
    • 不能把初始化器設定為一個物件,除非在初始化器中建立了一個新物件。
  7. 密封類和密封方法

    • C#允許把類和方法宣告為sealed。對於類,這表示不能繼承該類;對於方法,這表示不能重寫該方法。
    • 在把類或方法標記為sealed時,最可能的情形是:如果要對庫、類或自己編寫的其他類作用域之外的類或方法進行操作,則重寫某些功能會導致程式碼混亂。也可以因商業原因把類或方法標記為sealed,以防第三方以違反授權協議的方式擴充套件該類。但一般情況下,在把類或成員標記為sealed時要小心,因為這麼做會嚴重限制它的使用方式。即使認為它不能對繼承自一個類或重寫類的某個成員發揮作用,仍有可能在將來的某個時刻,有人會遇到我們沒有預料到的情形,此時這麼做就很有用。.Net基類庫大量使用了密封類 ,使希望從這些類中派生出自己的類的第三方開發人員無法訪問這些類。例如,string就是一個密封類。
  8. 約束

    • 泛型支援的幾種約束型別:
    約束 說明
    where T : struct 對於結構約束,型別T必須是值型別
    where T : class 類約束指定型別T必須是引用型別
    where T : IFoo 指定型別T必須實現介面IFoo
    where T : Foo 指定型別T必須派生自基類Foo
    where T : new() 這是一個建構函式約束,指定型別T必須有一個預設建構函式
    where T1 : T2 這個約束也可以指定型別T1派生自泛型型別T2。該約束也稱為裸型別約束
    • 只能為預設建構函式定義建構函式約束,不能為其他建構函式定義建構函式約束。
    • 在C#中,where子句的一個重要限制是,不能定義必須由泛型型別實現的運算子。運算子不能再借口中定義。在where子句中,只能定義基類、介面和預設建構函式。
  9. 複製陣列

    • 如果陣列的元素是值型別,呼叫Clone()方法就會複製所有值。如,int[] intArray1 = {1, 2}; int[] intArray2 = (int[])intArray1.Clone();其中intArray2陣列的元素也變成了{1, 2}
    • 如果陣列包含引用型別,則不復制元素,而只複製引用。
    • 除了使用Clone()方法之外,還可以使用Array.Copy()方法建立淺表副本。
    • Clone()方法和Copy()方法有一個重要區別:Clone()方法會建立一個新陣列,而Copy()方法必須傳遞階數相同且有足夠元素的已有陣列。
    • 如果需要包含引用型別的陣列的深層副本,就必須迭代陣列並建立新物件。
  10. Array類使用QuickSort演算法對陣列中的元素進行排序。Array類中的Sort()方法需要陣列中的元素實現IComparable介面。簡單型別(如System.String和System.Int32)已經實現了IComparable介面。

  11. 元組

    • 數組合並了相同型別的物件,而元組合並了不同型別的物件。
    • .NET 4定義了8個泛型Tuple類和一個靜態Tuple類,不同泛型Tuple類支援不同數量的元素。例如,Tuple<T1>包含一個元素,Tuple<T1, T2>包含兩個元素,以此類推。
    • 程式碼示例:

       View Code

       

    • 如果元組包含項超過8個,就可以使用帶8個引數的Tuple類定義。最後一個模板引數是TRest,表示必須給它傳遞一個元組,這樣就可以建立帶任意個引數的元組了。示例:

       View Code

       

  12. 運算子

    • is運算子:可以檢查物件是否與特定的型別相容。“相容”表示物件是該型別或者派生自該型別。
    • as運算子:用於執行引用型別的顯示型別轉換。如果要轉換的型別與制定的型別相容,轉換就會成功進行;如果型別不相容,as運算子就會返回null值。
    • sizeof運算子:使用該運算子可以確定棧中值型別需要的長度(單位是位元組);如果對於複雜型別(和非基元型別)使用該運算子,就需要把程式碼寫在unsafe塊中,如:unsafe{Console.WriteLine(sizeof(Customer));}
    • 可空型別和運算子:通常可空型別與一元或二元運算子一起使用時,如果其中一個運算元或兩個運算元都是null,其結果就是null。如: 
      int? a = null; 
      int? b = a + 4; // b = null 
      int? c = a * 5; // c = null
    • 空合併運算子(??):該運算子提供了一種快捷方式,可以在處理可空型別和引用型別時表示null可能的值。這個運算子放在兩個運算元之間,第一個運算元必須是一個可空型別或者引用型別;第二個運算元必須與第一個運算元的型別相同,或者可以隱含地轉換為第一個運算元的型別。
  13. 比較引用型別的相等性

    • ReferenceEquals()方法:該方法是一個靜態方法,測試兩個引用是否引用類的同一個例項,特別是兩個引用是否包含記憶體中的相同地址。如果提供的兩個引用引用同一個物件例項,則返回true,否則返回false。但是它認為null等於null。另外,該方法在應用於值型別時,它總是返回false,因為為了呼叫這個方法,值型別需要裝箱到物件中。
    • 虛擬的Equals()方法:Equals()虛擬版本的System.Object實現程式碼也可以比較引用。但因為這個方法是虛擬的,所以可以在自己的類中重寫它,從而按值來比較物件。特別是如果希望類的例項用作字典中的鍵,就需要重寫這個方法,以比較相關值。否則,根據重寫Object.GetHashCode()的方式,包含物件的字典類要麼不工作,要麼工作的效率非常低。在重寫Equals()方法時要注意,重寫的程式碼不會丟擲異常。同理,這是因為如果丟擲異常,字典類就會出問題,一些在內部呼叫這個方法的.NET基類也可能出問題。
    • 靜態的Equals()方法:Equals()的靜態版本與其虛擬例項版本的作用相同,其區別是靜態版本帶有兩個引數,並對它們進行相等比較。這個方法可以處理兩個物件中有一個是null的情況,因此,如果一個物件可能是null,這個方法就可以丟擲異常,提供額外保護。靜態過載版本首先要檢查它傳遞的引用是否為null。如果他們都是null,就返回true(因為nullnull相等)。如果只有一個引用是null,就返回false。如果兩個引用實際上引用了某個物件,它就呼叫Equals()的虛擬例項版本。這表示在重寫Equals()的例項版本時,其效果相當於也重寫了靜態版本。
    • 比較運算子(==):最好將比較運算子看作嚴格的值比較和嚴格的引用比較之間的中間選項。在大多數情況下,下面的程式碼表示正在比較引用:bool b = (x == y);// x, y object references
  14. 運算子過載

    • 運算子過載的宣告方式與方法相同,但operator關鍵字告訴編譯器,它實際上是一個自定義的運算子過載,後面是相關運算子的實際符號,返回型別是在使用這個運算子時獲得的型別。
    • 對於二元運算子(它帶兩個引數),如+-運算子,第一個引數是運算子左邊的值,第二個引數是運算子右邊的值。
    • 一般把運算子左邊的引數命名為lhs,運算子右邊的引數命名為rhs
    • C#要求所有的運算子過載都宣告為publicstatic,這表示它們與它們的類或結構相關聯,而不是與某個特定例項相關聯,所以運算子過載的程式碼體不能訪問非靜態類成員,也不能訪問this識別符號。
    • C#語言要求成對過載比較運算子。即,如果過載了==,也就必須過載!=;否則會產生編譯錯誤。另外,比較運算子必須返回布林型別的值。這是它們與算術運算子的根本區別。
    • 在過載==!=時,還必須過載從System.Object中繼承的Equals()和GetHashCode()方法,否則會產生一個編譯警告。原因是Equals()方法應實現與==運算子相同型別的相等邏輯。
  15. 委託

    • 理解委託的一個要點是它們的型別安全性非常高。
    • 理解委託的一種好方式是把委託當作這樣一件事,它給方法的簽名和返回型別指定名稱。
    • Action 
      • Action是無返回值的泛型委託。
      • Action表示無參,無返回值的委託
      • Action<int,string>表示有傳入引數int,string無返回值的委託
      • Action<int,string,bool>表示有傳入引數int,string,bool無返回值的委託
      • Action<int,int,int,int>表示有傳入4個int型引數,無返回值的委託
      • Action至少0個引數,至多16個引數,無返回值。
    • Func 
      • Func是有返回值的泛型委託
      • Func<int>表示無參,返回值為int的委託
      • Func<object,string,int>表示傳入引數為objectstring返回值為int的委託
      • Func<object,string,int>表示傳入引數為objectstring返回值為int的委託
      • Func<T1,T2,,T3,int>表示傳入引數為T1,T2,T3(泛型)返回值為int的委託
      • Func至少0個引數,至多16個引數,根據返回值泛型返回。必須有返回值,不可void
  16. Lambda表示式

    • 只要有委託引數型別的地方,就可以使用Lambda表示式。或者說Lambda表示式可以用於型別是一個委託的任意地方。
    • 如果只有一個引數,只寫出引數名就足夠了。如果委託使用多個引數,就把引數名放在小括號中。為了方便可以在小括號中給變數新增引數型別。
    • 如果Lambda表示式只有一條語句,在方法塊內就不需要花括號和return語句,因為編譯器會新增一條隱式return語句。
  17. 正則表示式

    • 常用的特定字元和轉義序列如下表:
    符 號 含 義 示 例 匹配的示例
    ^ 輸入文字的開頭 ^B B,但只能是文字中的第一個字元
    $ 輸入文字的結尾 X$ X,但只能是文字中的最後一個字元
    . 除了換行符(\n)以外的所有單個字元 i.ation isation、ization
    * 可以重複0次或多次的前導字元 ra*t rt、rat、raat和raaat等
    + 可以重複1次或多次的前導字元 ra+t rat、raat和raaat等(但不能是rt)
    ? 可以重複0次或1次的前導字元 ra?t 只有rt和rat匹配
    \s 任何空白字元 \sa [space]a、\ta、\na(其中[space]表示空格,\t和\n都是轉移字元)
    \S 任何不是空白的字元 \SF aF、rF、cF,但不能是\tF
    \b 字邊界 ion\b 以ion結尾的任何字
    \B 不是字邊界的任意位置 \BX\B 字中間的任何X
    • 可以把替換的字元放在方括號中,請求匹配包含這些字元。例如,[1|c]表示字元可以是1c。在方括號中,也可以指定一個範圍,例如[a-z]表示所有的小寫字母,[A-E]表示A~E之間的所有大寫字母(包括字母AE),[0-9]表示一個數字。如果要搜尋一個整數,就可以編寫[0-9]+
  18. 集合

    • 連結串列。LinkedList<T>是一個雙向連結串列,其元素指向它前面和後面的元素。其特點是:插入快,查詢慢。
    • 有序列表。如果需要基於鍵對所需集合排序,就可以使用SortedList<TKey,TValue>類,這個類按照鍵給元素排序。
    • 字典。 
      • 字典的主要特徵是能根據鍵快速查詢值。也可以自由新增和刪除元素,這點有點像List<T>類,但沒有在記憶體中移動後續元素的效能開銷。
      • 用作字典中鍵的型別必須重寫Object類的GetHashCode()方法。只要字典類需要確定元素的位置,它就要呼叫GetHashCode()方法。GetHashCode()方法返回的int由字典用於計算在對應位置放置元素的索引。
      • 字典的效能取決於GetHashCode()方法的實現程式碼。
      • 除了實現GetHashCode()方法之外,鍵型別還必須實現IEquatable<T>.Equals()方法,或重寫Object類的Equals()方法。因為不同的鍵物件可能返回相同的雜湊程式碼,所以字典使用Equals()方法來比較鍵。
  19. GetHashCode()方法的實現程式碼必須滿足如下要求:

    • 相同的物件應總是返回相同的值。
    • 不同的物件可以返回相同的值。
    • 它應執行得比較快,計算的開銷不大。
    • 它不能丟擲異常。
    • 它應至少使用一個例項欄位。
    • 雜湊程式碼值應平均分佈在int可以儲存的整個數字範圍上。
    • 雜湊程式碼最好在物件的生存期中不發生變化。
  20. 如果為Equals()方法提供了重寫版本,但沒有提供GetHashCode()方法的重寫版本,C#編譯器就會顯示一個編譯警告。

  21. LINQ

    • 查詢表示式必須以from子句開頭,以selectgroup子句結束。在這兩個子句之間,可以使用whereorderbyjoinlet和其他from子句。
    • LINQIEnumerable<T>介面提供了各種擴充套件方法,以便使用者在實現了該介面的任意集合上使用LINQ查詢。
  22. 釋放非託管的資源

    • 在定義一個類時,可以使用兩種機制來自動釋放非託管的資源。這些機制常常放在一起實現,因為每種機制都為問題提供了略微不同的解決辦法。
    • 釋放非託管資源的兩種機制:宣告一個解構函式(或終結器);在類中實現System.IDisposable介面。
  23. 解構函式

    • 在銷燬C++物件時,其解構函式會立即執行。但由於使用C#時垃圾回收器的工作方式,無法確定C#物件的解構函式合適執行。所以,不能在解構函式中放置需要在某一時刻執行的程式碼,也不應使用能以任意順序對不同類的例項呼叫的解構函式。如果物件佔用了寶貴而重要的資源,應儘快釋放這些資源,此時就不能等待垃圾回收器來釋放了。
    • C#解構函式的實現會延遲物件最終從記憶體中刪除的時間。沒有解構函式的物件會在垃圾回收器的一次處理中從記憶體中刪除,但有解構函式的物件需要兩次處理才能銷燬:第一次呼叫解構函式時,沒有刪除物件,第二次呼叫才真正刪除物件。另外,執行庫使用一個執行緒來執行所有物件的Finalize()方法。如果頻繁使用解構函式,而且使用它們執行長時間的清理任務,對效能的影響就會非常顯著。
  24. IDisposable介面

    • 在C#中,推薦使用System.IDisposable介面替代解構函式。IDisposable介面定義了一種模式(具有語言級的支援),該模式為釋放非託管的資源提供了確定的機制,並避免產生解構函式固有的與垃圾回收器相關的問題。IDisposable介面聲明瞭一個Dispos()方法,它不帶引數,返回void
    • Dispose()方法的實現程式碼顯式地釋放由物件直接使用的所有非託管資源,並在所有也實現IDisposable介面的封裝物件上呼叫Dispose()方法。這樣,Dispose()方法為何時釋放非託管資源提供了精確的控制。

以上是《C#高階程式設計》前二十章的讀書筆記。筆記摘錄了筆者認為易忘的知識點,方便以後查閱和複習。摘抄整理過程中難免疏忽和遺漏,如有錯誤不當之處,請不吝指出,在此感激不盡!


宣告:本文歡迎轉載和分享,但是請尊重作者的勞動成果,轉載分享時請註明出處:http://www.cnblogs.com/davidsheh/p/5236686.html 。