《C#高级编程》读书笔记
本文已同步發表在博客園:http://www.cnblogs.com/davidsheh/p/5236686.html
C#類型的取值范圍
訪問限制符
| public | 所有類型或成員 | 任何代碼均可以訪問該項 |
| protected | 類型和內嵌類型的所有成員 | 只有派生的類型能夠訪問該項 |
| internal | 所有類型或成員 | 只能在包含它的程序集中訪問該項 |
| private | 類型和內嵌類型的所有成員 | 只能在它所屬的類型中訪問該項 |
| protected internal | 類型和內嵌類型的所有成員 | 只能在包含它的程序集和派生類型的任何代碼中訪問該項 |
C#常見的修飾符
| new | 函數成員 | 成員用相同的簽名隱藏繼承的成員 |
| static | 所有成員 | 成員不作用于類的具體實例 |
| virtual | 僅函數成員 | 成員可以由派生類重寫 |
| abstract | 僅函數成員 | 虛擬成員定義了成員的簽名,但沒有提供實現代碼 |
| override | 僅函數成員 | 成員重寫了繼承的虛擬或抽象成員 |
| sealed | 類、方法和屬性 | 對于類,不能繼承自密封類。對于屬性和方法,成員重寫已繼承的虛擬成員,但任何派生類中的任何成員都不能重寫該成員。該修飾符必須與override一起使用 |
| extern | 僅靜態[DllImport]方法 | 成員在外部用另一種語言實現 |
結構體
- 結構是值類型,不是引用類型。
- 存儲在棧中或存儲為內聯(inline)(如果它們是存儲在堆中的另一個對象的一部分),其生存期的限制與簡單的數據類型一樣。
- 結構體不支持繼承。
- 對于結構構造函數的工作方式有一些區別。尤其是編譯器總是提供一個無參數的默認構造函數,它是不允許替換的。
- 使用結構,可以指定字段如何在內存中的布局。
- 注意,因為結構是值類型,所以new運算符與類和其他引用類型的工作方式不同。new運算符并不分配堆中的內存,而是只調用相應的構造函數,根據傳送給它的參數初始化所有的字段。
- 結構遵循其他數據類型都遵循的規則:在使用前所有的元素都必須進行初始化。在結構上調用new運算符,或者給所有的字段分別賦值,結構就完全初始化了。當然,如果結構定義為類的成員字段,在初始化包含的對象時,該結構會自動初始化為0。
- 結構是會影響性能的值類型,但根據使用結構的方式,這種影響可能是正面的,也可能是負面的。正面的影響是為結構分配內存時,速度非常快,因為它們將內聯或者保存在棧中。在結構超出了作用域被刪除時,速度也很快。負面影響是,只要把結構作為參數來傳遞或者把一個結構賦予另一個結構(如A=B,其 中A和B是結構),結構的所有內容就被復制,而對于類,則只復制引用。這樣就會有性能損失,根據結構的大小,性能損失也不同。注意,結構主要用于小的數據結構。但當把結構作為參數傳遞給方法時,應把它作為ref參數傳遞,以避免性能損失————此時只傳遞了結構在內存中的地址,這樣傳遞速度就與在類中的傳遞速度一樣快了。但如果這樣做,就必須注意被調用的方法可以改變結構的值。
- 結構不是為繼承設計的。這意味著:它不能從一個結構中繼承。唯一的例外是對應的結構(和C#中的其他類型一樣)最終派生于類System.Object。因此,結構也可以訪問System.Object的方法。在結構中,甚至可以重寫System.Object中的方法————如重寫ToString()方法。結構的繼承鏈是:每個結構派生自System.ValueType類,System.ValueType類又派生自System.Object。ValueType并沒有給Object添加任何新成員,但提供了一些更適合結構的實現方式。注意,不能為結構提供其他基類,每個結構都派生自ValueType。
- 為結構定義構造函數的方式與為類定義構造函數的方式相同,但 不允許定義無參數的構造函數。默認構造函數把數值字段都初始化為0,把引用類型字段初始化為null,且總是隱式地給出,即使提供了其他帶參數的構造函數,也是如此。提供字段的初始值也不能繞過默認構造函數。
擴展方法
- 擴展方法允許改變一個類,但不需要該類的源代碼。所以使用擴展方法的情景之一是,當不知道類的源碼或者不想修改該類的源碼卻想擴展該類,就可以用擴展方法。
- 擴展方法是靜態方法,它是類的一部分,但實際上沒有放在類的源代碼中。
- 擴展方法需放在靜態類中。
- 對于擴展方法,第一個參數是要擴展的類型,它放在this關鍵字的后面。
- 在擴展方法中,可以訪問所擴展類型的所有共有方法和屬性。
- 如果擴展方法與類中的某個方法同名,就從來不會調用擴展方法。類中已有的任何實例方法優先。
var關鍵字。編譯器可以根據變量的初始化值“推斷 ” 變量的類型。使用var關鍵字需要遵循的一些規則:
- 變量必須初始化。否則,編譯器就沒有推斷變量類型的依據。
- 初始化器不能為空。
- 初始化器必須放在表達式中。
- 不能把初始化器設置為一個對象,除非在初始化器中創建了一個新對象。
密封類和密封方法
- C#允許把類和方法聲明為sealed。對于類,這表示不能繼承該類;對于方法,這表示不能重寫該方法。
- 在把類或方法標記為sealed時,最可能的情形是:如果要對庫、類或自己編寫的其他類作用域之外的類或方法進行操作,則重寫某些功能會導致代碼混亂。也可以因商業原因把類或方法標記為sealed,以防第三方以違反授權協議的方式擴展該類。但一般情況下,在把類或成員標記為sealed時要小心,因為這么做會嚴重限制它的使用方式。即使認為它不能對繼承自一個類或重寫類的某個成員發揮作用,仍有可能在將來的某個時刻,有人會遇到我們沒有預料到的情形,此時這么做就很有用。.Net基類庫大量使用了密封類 ,使希望從這些類中派生出自己的類的第三方開發人員無法訪問這些類。例如,string就是一個密封類。
約束
- 泛型支持的幾種約束類型:
| 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子句中,只能定義基類、接口和默認構造函數。
復制數組
- 如果數組的元素是值類型,調用Clone()方法就會復制所有值。如,int[] intArray1 = {1, 2}; int[] intArray2 = (int[])intArray1.Clone();其中intArray2數組的元素也變成了{1, 2}
- 如果數組包含引用類型,則不復制元素,而只復制引用。
- 除了使用Clone()方法之外,還可以使用Array.Copy()方法創建淺表副本。
- Clone()方法和Copy()方法有一個重要區別:Clone()方法會創建一個新數組,而Copy()方法必須傳遞階數相同且有足夠元素的已有數組。
- 如果需要包含引用類型的數組的深層副本,就必須迭代數組并創建新對象。
Array類使用QuickSort算法對數組中的元素進行排序。Array類中的Sort()方法需要數組中的元素實現IComparable接口。簡單類型(如System.String和System.Int32)已經實現了IComparable接口。
元組
- 數組合并了相同類型的對象,而元組合并了不同類型的對象。
- .NET 4定義了8個泛型Tuple類和一個靜態Tuple類,不同泛型Tuple類支持不同數量的元素。例如,Tuple<T1>包含一個元素,Tuple<T1, T2>包含兩個元素,以此類推。
代碼示例:
public class TupleExample {static void Main(){TupleExample example = new TupleExample();var result = example.Divide(5, 2);Console.WriteLine("result of division: {0}, reminder: {1}", result.Item1, result.Item2);}public static Tuple< int, int > Divide(int dividend, int divisor){int result = dividend / divisor;int reminder = dividend % divisor;return TupleExample.Create<int, int>(result, reminder);} }如果元組包含項超過8個,就可以使用帶8個參數的Tuple類定義。最后一個模板參數是TRest,表示必須給它傳遞一個元組,這樣就可以創建帶任意個參數的元組了。示例:
var tuple = Tuple.Create<string, string, string, int, int, int, double, Tuple<int, int>>("Stephanie", "Alina", "Nagel", 2009, 6, 2, 1.37, Tuple.Create<int, int>(52, 3490));
運算符
- 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可能的值。這個運算符放在兩個操作數之間,第一個操作數必須是一個可空類型或者引用類型;第二個操作數必須與第一個操作數的類型相同,或者可以隱含地轉換為第一個操作數的類型。
比較引用類型的相等性
- ReferenceEquals()方法:該方法是一個靜態方法,測試兩個引用是否引用類的同一個實例,特別是兩個引用是否包含內存中的相同地址。如果提供的兩個引用引用同一個對象實例,則返回true,否則返回false。但是它認為null等于null。另外,該方法在應用于值類型時,它總是返回false,因為為了調用這個方法,值類型需要裝箱到對象中。
- 虛擬的Equals()方法:Equals()虛擬版本的System.Object實現代碼也可以比較引用。但因為這個方法是虛擬的,所以可以在自己的類中重寫它,從而按值來比較對象。特別是如果希望類的實例用作字典中的鍵,就需要重寫這個方法,以比較相關值。否則,根據重寫Object.GetHashCode()的方式,包含對象的字典類要么不工作,要么工作的效率非常低。在重寫Equals()方法時要注意,重寫的代碼不會拋出異常。同理,這是因為如果拋出異常,字典類就會出問題,一些在內部調用這個方法的.NET基類也可能出問題。
- 靜態的Equals()方法:Equals()的靜態版本與其虛擬實例版本的作用相同,其區別是靜態版本帶有兩個參數,并對它們進行相等比較。這個方法可以處理兩個對象中有一個是null的情況,因此,如果一個對象可能是null,這個方法就可以拋出異常,提供額外保護。靜態重載版本首先要檢查它傳遞的引用是否為null。如果他們都是null,就返回true(因為null與null相等)。如果只有一個引用是null,就返回false。如果兩個引用實際上引用了某個對象,它就調用Equals()的虛擬實例版本。這表示在重寫Equals()的實例版本時,其效果相當于也重寫了靜態版本。
- 比較運算符(==):最好將比較運算符看作嚴格的值比較和嚴格的引用比較之間的中間選項。在大多數情況下,下面的代碼表示正在比較引用:bool b = (x == y);// x, y object references
運算符重載
- 運算符重載的聲明方式與方法相同,但operator關鍵字告訴編譯器,它實際上是一個自定義的運算符重載,后面是相關運算符的實際符號,返回類型是在使用這個運算符時獲得的類型。
- 對于二元運算符(它帶兩個參數),如+和-運算符,第一個參數是運算符左邊的值,第二個參數是運算符右邊的值。
- 一般把運算符左邊的參數命名為lhs,運算符右邊的參數命名為rhs。
- C#要求所有的運算符重載都聲明為public和static,這表示它們與它們的類或結構相關聯,而不是與某個特定實例相關聯,所以運算符重載的代碼體不能訪問非靜態類成員,也不能訪問this標識符。
- C#語言要求成對重載比較運算符。即,如果重載了==,也就必須重載!=;否則會產生編譯錯誤。另外,比較運算符必須返回布爾類型的值。這是它們與算術運算符的根本區別。
- 在重載==和!=時,還必須重載從System.Object中繼承的Equals()和GetHashCode()方法,否則會產生一個編譯警告。原因是Equals()方法應實現與==運算符相同類型的相等邏輯。
委托
- 理解委托的一個要點是它們的類型安全性非常高。
- 理解委托的一種好方式是把委托當作這樣一件事,它給方法的簽名和返回類型指定名稱。
- 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>表示傳入參數為object, string返回值為int的委托
- Func<object,string,int>表示傳入參數為object, string返回值為int的委托
- Func<T1,T2,,T3,int>表示傳入參數為T1,T2,T3(泛型)返回值為int的委托
- Func至少0個參數,至多16個參數,根據返回值泛型返回。必須有返回值,不可void
Lambda表達式
- 只要有委托參數類型的地方,就可以使用Lambda表達式。或者說Lambda表達式可以用于類型是一個委托的任意地方。
- 如果只有一個參數,只寫出參數名就足夠了。如果委托使用多個參數,就把參數名放在小括號中。為了方便可以在小括號中給變量添加參數類型。
- 如果Lambda表達式只有一條語句,在方法塊內就不需要花括號和return語句,因為編譯器會添加一條隱式return語句。
正則表達式
- 常用的特定字符和轉義序列如下表:
| ^ | 輸入文本的開頭 | ^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]表示字符可以是1或c。在方括號中,也可以指定一個范圍,例如[a-z]表示所有的小寫字母,[A-E]表示A~E之間的所有大寫字母(包括字母A和E),[0-9]表示一個數字。如果要搜索一個整數,就可以編寫[0-9]+。
集合
- 鏈表。LinkedList<T>是一個雙向鏈表,其元素指向它前面和后面的元素。其特點是:插入快,查找慢。
- 有序列表。如果需要基于鍵對所需集合排序,就可以使用SortedList<TKey,TValue>類,這個類按照鍵給元素排序。
- 字典。
- 字典的主要特征是能根據鍵快速查找值。也可以自由添加和刪除元素,這點有點像List<T>類,但沒有在內存中移動后續元素的性能開銷。
- 用作字典中鍵的類型必須重寫Object類的GetHashCode()方法。只要字典類需要確定元素的位置,它就要調用GetHashCode()方法。GetHashCode()方法返回的int由字典用于計算在對應位置放置元素的索引。
- 字典的性能取決于GetHashCode()方法的實現代碼。
- 除了實現GetHashCode()方法之外,鍵類型還必須實現IEquatable<T>.Equals()方法,或重寫Object類的Equals()方法。因為不同的鍵對象可能返回相同的散列代碼,所以字典使用Equals()方法來比較鍵。
GetHashCode()方法的實現代碼必須滿足如下要求:
- 相同的對象應總是返回相同的值。
- 不同的對象可以返回相同的值。
- 它應執行得比較快,計算的開銷不大。
- 它不能拋出異常。
- 它應至少使用一個實例字段。
- 散列代碼值應平均分布在int可以存儲的整個數字范圍上。
- 散列代碼最好在對象的生存期中不發生變化。
如果為Equals()方法提供了重寫版本,但沒有提供GetHashCode()方法的重寫版本,C#編譯器就會顯示一個編譯警告。
LINQ
- 查詢表達式必須以from子句開頭,以select或group子句結束。在這兩個子句之間,可以使用where、orderby、join、let和其他from子句。
- LINQ為IEnumerable<T>接口提供了各種擴展方法,以便用戶在實現了該接口的任意集合上使用LINQ查詢。
釋放非托管的資源
- 在定義一個類時,可以使用兩種機制來自動釋放非托管的資源。這些機制常常放在一起實現,因為每種機制都為問題提供了略微不同的解決辦法。
- 釋放非托管資源的兩種機制:聲明一個析構函數(或終結器);在類中實現System.IDisposable接口。
析構函數
- 在銷毀C++對象時,其析構函數會立即運行。但由于使用C#時垃圾回收器的工作方式,無法確定C#對象的析構函數合適執行。所以,不能在析構函數中放置需要在某一時刻運行的代碼,也不應使用能以任意順序對不同類的實例調用的析構函數。如果對象占用了寶貴而重要的資源,應盡快釋放這些資源,此時就不能等待垃圾回收器來釋放了。
- C#析構函數的實現會延遲對象最終從內存中刪除的時間。沒有析構函數的對象會在垃圾回收器的一次處理中從內存中刪除,但有析構函數的對象需要兩次處理才能銷毀:第一次調用析構函數時,沒有刪除對象,第二次調用才真正刪除對象。另外,運行庫使用一個線程來執行所有對象的Finalize()方法。如果頻繁使用析構函數,而且使用它們執行長時間的清理任務,對性能的影響就會非常顯著。
IDisposable接口
- 在C#中,推薦使用System.IDisposable接口替代析構函數。IDisposable接口定義了一種模式(具有語言級的支持),該模式為釋放非托管的資源提供了確定的機制,并避免產生析構函數固有的與垃圾回收器相關的問題。IDisposable接口聲明了一個Dispos()方法,它不帶參數,返回void。
- Dispose()方法的實現代碼顯式地釋放由對象直接使用的所有非托管資源,并在所有也實現IDisposable接口的封裝對象上調用Dispose()方法。這樣,Dispose()方法為何時釋放非托管資源提供了精確的控制。
以上是《C#高級編程》前二十章的讀書筆記。筆記摘錄了筆者認為易忘的知識點,方便以后查閱和復習。摘抄整理過程中難免疏忽和遺漏,如有錯誤不當之處,請不吝指出,在此感激不盡!
聲明:本文歡迎轉載和分享,但是請尊重作者的勞動成果,轉載分享時請注明出處:http://blog.csdn.net/wenxin2011/article/details/50790837。
總結
以上是生活随笔為你收集整理的《C#高级编程》读书笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 把html转换为json数据,HTML转
- 下一篇: 华为OD机试(20222023)专栏介绍