程式
程式集是程式碼進行編譯是的一個邏輯單元,把相關的程式碼和型別進行組合,然後生成PE檔案。程式集只是邏輯上的劃分
【公共語言執行庫CLR】載入器 管理 應用程式域,這種管理包括 將每個程式集載入到相應的應用程式域 以及 控制每個程式集中型別層次結構的記憶體佈局
一個程式執行起來以後,有一個應用程式域(AppDomain),在這個應用程式域(AppDomain)中放了我們用到的所有程式集(Assembly)。我們所寫的所有程式碼都會編譯到【程式集】檔案(.exe .dll)中,並在執行時以【Assembly物件】方式載入到記憶體中執行,每個類(Class Interface)以【Type物件】方式載入到記憶體,類的成員(方法,欄位,屬性,事件,構造器)載入到記憶體也有相應的物件。
一、 c#底層
跨語言
只要是面向.NET平臺的程式語言((C#、Visual Basic、C++/CLI、Eiffel、F#、IronPython、IronRuby、PowerBuilder、Visual COBOL 以及 Windows PowerShell)),用其中一種語言編寫的型別可以無縫地用在另一種語言編寫的應用程式中的互操作性。
例子:C#呼叫vb生成的dll中的一個類
需遵循它就是
公共語言規範 - Common Language Specification ,簡稱CLS
跨平臺:
一次編譯,不需要任何程式碼修改,應用程式就可以執行在任意有.NET框架實現的平臺上,即程式碼不依賴於作業系統,也不依賴硬體環境。
CTS(Common Type System 公共型別系統)
CLS是CTS(Common Type System 公共型別系統)這個體系中的子集
CLI(Common Language Infrastructure)公共語言基礎結構。
軟已經將CTS和.NET的一些其它元件,提交給ECMA以成為公開的標準,最後形成的標準稱為cli
由微軟開發的類庫統稱為:FCL,Framework Class Library ,.NET框架類庫
組合語言
不同廠商的CPU有著不同的指令集,為了克服面向CPU的指令集的難讀、難編、難記和易出錯的缺點,後來就出現了面向特定CPU的特定組合語言, 比如我打上這樣的x86彙編指令 mov ax,bx ,然後用上用機器碼做的彙編器,它將會被翻譯成 1000100111011000 這樣的二進位制01格式的機器指令.
用C語言寫的程式碼檔案,會被C編譯器先轉換成對應平臺的彙編指令,再轉成機器碼,最後將這些過程中產生的中間模組連結成一個可以被作業系統執行的程式。
CLR
實際上,.NET不僅提供了自動記憶體管理的支援,他還提供了一些列的如型別安全、應用程式域、異常機制等支援,這些 都被統稱為CLR公共語言執行庫。
CLR是.NET型別系統的基礎,所有的.NET技術都是建立在此之上,熟悉它可以幫助我們更好的理解框架元件的核心、原理。
在我們執行託管程式碼之前,總會先執行這些執行庫程式碼,透過執行庫的程式碼呼叫,從而構成了一個用來支援託管程式的執行環境,進而完成諸如不需要開發人員手動管理記憶體,一套程式碼即可在各大平臺跑的這樣的操作。
程式集
可執行檔案(.exe檔案)和 類庫檔案(.dll檔案)。
在VS開發環境中,一個解決方案可以包含多個專案,而每個專案就是一個程式集。
程式集的結構:
程式集後設資料,型別後設資料,MSIL程式碼,資源。
①程式集後設資料,程式集後設資料也叫清單,它記錄了程式集的許多重要資訊,是程式集進行自我說明的核心文件。當程式執行時,CLR 透過這份清單就能獲取執行程式集所必需的全部資訊。清單中主要主要包含如下資訊:標識資訊(包括程式集的名稱、版本、文化和公鑰等);檔案列表(程式集由哪些檔案組成);引用程式集列表(該程式集所引用的其他程式集);一組許可請求(執行這個程式集需要的許可)。
②型別後設資料,型別後設資料列舉了程式集中包含的型別資訊,詳細說明了程式集中定義了哪些類,每個類包含哪些屬性和方法,每個方法有哪些引數和返回值型別,等等。
③MSIL程式碼,程式集後設資料和型別後設資料只是一些輔助性的說明資訊,它們都是為描述MSIL程式碼而存在的。MSIL 程式碼是程式集的真正核心部分,正是它們實現了程式集的功能。比如在“Animals”專案中,五個動物類的C#程式碼最終都被轉換為MSIL 程式碼,儲存在程式集Animals.dll 中,當執行程式時,就是透過這些MSIL 程式碼繪製動物影像的。
④資源,程式集中還可能包含影像、圖示、聲音等資源。
私有程式集和共享程式集
私有程式集是僅供單個軟體使用的程式集,安裝很簡單,只需把私有程式集複製到軟體包所在資料夾中即可。而那些被不同軟體共同使用的程式就是共享程式集,.NET類庫的程式集就是共享程式集,共享程式集為不同的程式所共用,所以它的部署就不像私有程式集那麼簡單,必須考慮命名衝突和版本衝突等問題。解決這些問題的辦法是把共享程式集放在系統的一個特定資料夾內,這個特定資料夾稱為全域性程式集快取記憶體(GAC)。這個過程可用專門的.NET 工具完成
託管程式碼
Clr管理記憶體、處理安全性、垃圾回收,利用其託管功能讓.net自己與作業系統進行互動
資源
值型別
- 垃圾收集器釋放儲存在託管堆中的託管物件,但不釋放本機堆中的物件。必須由開發人員自己釋放它們。
- 32位處理器上的每個程序都可以使用4GB的記憶體
- 選擇x86時,就除錯執行在32位和64位系統上的32位應用程式;選擇x64時,就除錯執行在64位系統上的64位應用程式
- 棧實際上是向下填充的,即從高記憶體地址向低記憶體地址填充。當資料入棧後,棧指標就會隨之調整,以始終指向下一個空閒儲存單元
- 首先,宣告一個Customer引用arabel,在棧上給這個引用分配儲存空間,但這僅是一個引用,而不是實際的Customer物件
- 分配堆上的記憶體,以儲存Customer物件(一個真正的物件,不只是一個地址)。然後把變數arabel的值設定為分配給新Customer物件的記憶體地址
- 為了在堆上找到儲存新Customer物件的一個儲存位置,.NET執行庫在堆中搜尋,選取第一個未使用的且包含Customer物件個位元組的連續塊。
- 只要保持對資料的引用,該資料就肯定存在於堆上。
- 只要它釋放了能釋放的所有物件,就會把其他物件移動回堆的端部,再次形成一個連續的記憶體塊。因此,堆可以繼續像棧那樣確定在什麼地方儲存新物件。當然,在移動物件時,這些物件的所有引用都需要用正確的新地址來更新,但垃圾回收器也會處理更新問題。
- NET下,較大物件有自己的託管堆,稱為大物件堆。使用大於85000個位元組的物件時,它們就會放在這個特殊的堆上,而不是主堆上。.NET應用程式不知道兩者的區別,因為這是自動完成的。其原因是在堆上壓縮大物件是比較昂貴的,因此駐留在大物件堆上的物件不執行壓縮過程。
引用型別
垃圾回收
強引用和弱引用
1.如果物件相互引用,但沒有在根表中引用,例如,物件A引用B, B引用C, C引用A,則GC可以銷燬所有這些物件
2. 在應用程式程式碼內例項化一個類或結構時,只要有程式碼引用它,就會形成強引用。例如,如果有一個類MyClass,並建立了一個變數myClassVariable來引用該類的物件,那麼只要myClassVariable在作用域內,就存在對MyClass物件的強引用
- 垃圾回收器不知道如何釋放非託管的資源(例如,檔案控制代碼、網路連線和資料庫連線
- 在底層的.NET體系結構中,這些函式稱為終結器(finalizer)。在C#中定義解構函式時,編譯器傳送給程式集的實際上是Finalize()方法。
- 沒有解構函式的物件會在垃圾回收器的一次處理中從記憶體中刪除,但有解構函式的物件需要兩次處理才能銷燬:第一次呼叫解構函式時,沒有刪除物件,第二次呼叫才真正刪除物件。另外,執行庫使用一個執行緒來執行所有物件的Finalize()方法。如果頻繁使用解構函式,而且使用它們執行長時間的清理任務,對效能的影響就會非常顯著。
非託管資源
二、語言基礎
1、變數
● 變數是類或結構中的欄位,如果沒有顯式初始化,則建立這些變數時,其預設值就是0。
●方法的區域性變數必須在程式碼中顯式初始化,之後才能在語句中使用它們的值。此時,初始化不是在宣告該變數時進行的,但編譯器會透過方法檢查所有可能的路徑,如果檢測到區域性變數在初始化之前就使用了其值,就會標記為錯誤。
3、一個是在類級別上定義的j,一個是在Main()中定義的j。這裡,在Main()方法中宣告的新變數j隱藏了同名的類級別變數,如果要引用類級別變數可以使用語法object.fieldname,
可以使用語法object.fieldname
2、常量
● 常量必須在宣告時初始化。指定了其值後,就不能再改寫了。
● 常量的值必須能在編譯時用於計算。因此,不能用從變數中提取的值來初始化常量。如果需要這麼做,應使用只讀欄位(詳見第3章)。
● 常量總是隱式靜態的。但注意,不必(實際上,是不允許)在常量宣告中包含修飾符static。
3、值型別和引用型別
值型別儲存在堆疊(stack)中,而引用型別儲存在託管堆(managed heap)上
整型
16進位制 需加上字首0x
浮點型別
編譯器一般假定該變數是double。如果想指定該值為float,可以在其後加上字元F(或f)
decimal型別
decimal型別不是基本型別,所以在計算時使用該型別會有效能損失。要把數字指定為decimal型別而不是double、float或整數型別,可以在數字的後面加上字元M(或m)
bool型別
如果變數(或函式的返回型別)宣告為bool型別,就只能使用值true或false。如果試圖使用0表示false,非0值表示true,就會出錯
字元型別
Object型別
String型別
流程控制
switch語句
- 也可以在switch語句中包含一條default子句,如果表示式不等於任何case子句的值,就執行default子句的程式碼
- 也可以在switch語句中包含一條default子句,如果表示式不等於任何case子句的值,就執行default子句的程式碼
- 如果一條case子句為空,就可以從這條case子句跳到下一條case子句,這樣就可以用相同的方式處理兩條或多條case子句了(不需要goto語句)。
Foreach
Goto
列舉
- 一旦程式碼編譯好,列舉就成為基本型別,與int和float類似
Using關鍵字
using關鍵字的另一個用途是給類和名稱空間指定別名
Main方法
C#前處理器指令
#define
類似於宣告一個變數,但這個變數並沒有真正的值,只是存在而已。這個符號不是實際程式碼的一部分,而只在編譯器編譯程式碼時存在
#undef正好相反——它刪除符號的定義
#if
當編譯器遇到#if指令後,將先檢查相關的符號是否存在,如果符號存在,就編譯#if子句中的程式碼。否則,編譯器會忽略所有的程式碼,直到遇到匹配的#endif指令為止
#warning和 # error
#region和#endregion
#line
#line指令可以用於改變編譯器在警告和錯誤資訊中顯示的檔名和行號資訊
#pragma
#pragma指令可以抑制或還原指定的編譯警告。與命令列選項不同,#pragma指令可以在類或方法級別實現,對抑制警告的內容和抑制的時間進行更精細的控制。
規則
識別符號
- 儘管可以包含數字字元,但它們必須以字母或下劃線開頭。2.
- 不能把C#關鍵字用作識別符號。
- 可以在識別符號的前面加上字首符號@
- 識別符號也可以包含Unicode字元,用語法\uXXXX來指定,其中XXXX是Unicode字元的4位十六進位制編碼
屬性和方法的使用
果該物件的外觀像變數,就應使用屬性來表示它
欄位
欄位的用法非常簡單。欄位應總是私有的,但在某些情況下也可以把常量或只讀欄位設定為公有。原因是如果把欄位設定為公有,就不利於在以後擴充套件或修改類。
三、類和物件
結構體(System.ValueType類)
- 結構不同於類,因為它們不需要在堆上分配空間(類是引用型別,總是儲存在堆(heap)上),而結構是值型別,通常儲存在棧(stack)上,另外,結構不支援繼承
- 都使用關鍵字new來宣告例項
- 因為結構是值型別,所以new運算子與類和其他引用型別的工作方式不同。new運算子並不分配堆中的記憶體,而是隻呼叫相應的建構函式,根據傳送給它的引數,初始化所有的欄位
- 對於結構,變數宣告實際上是為整個結構在棧中分配空間,所以就可以為它賦值了
- 只要把結構作為引數來傳遞或者把一個結構賦予另一個結構(如A=B,其中A和B是結構),結構的所有內容就被複制,而對於類,則只複製引用
- 但當把結構作為引數傳遞給方法時,應把它作為ref引數傳遞,以避免效能損失——此時只傳遞了結構在記憶體中的地址
類成員
屬性
屬性(property)的概念是:它是一個方法或一對方法,在客戶端程式碼看來,它(們)是一個欄位。
get訪問器不帶任何引數,且必須返回屬性宣告的型別。也不應為set訪問器指定任何顯式引數,但編譯器假定它帶一個引數,其型別也與屬性相同,並表示為value。
方法
如果params關鍵字與方法簽名定義的多個引數一起使用,則params只能使用一次,而且它必須是最後一個引數
靜態建構函式
- 編寫靜態建構函式的一個原因是,類有一些靜態欄位或屬性,需要在第一次使用類之前,從外部源中初始化這些靜態欄位和屬性。
只讀欄位
按值和按引用傳遞引數
2
12
1111.1吧
2
11111
列舉
- 預設情況下,enum的型別是int。這個基本型別可以改為其他整數型別(byte、short、int、帶符號的long和無符號變數)。命名常量的值從0開始遞增
- 部分方法:程式碼應該呼叫可能不存在的方法,用partial關鍵字宣告;因此不需要任何實現程式碼。如果沒有實現程式碼,編譯器將刪除這個方法呼叫:部分方法必須是void型別,否則編譯器在沒有實現程式碼的情況下無法刪除呼叫
部分類
擴充套件方法
- 擴充套件方法是靜態方法,它是類的一部分,但實際上沒有放在類的原始碼中
- 如果型別還定義了同名的例項方法,擴充套件方法就永遠不會使用。類中已有的任何例項方法都優先
MemberwiseClone()
四、繼承
1. 多重繼承允許一個類派生自多個類。C#不支援類的多重繼承,但允許介面的多重繼承。
2. 不能編碼實現結構的型別層次,但結構可以實現介面。換言之,結構並不支援實現繼承,但支援介面繼承
3. 如果類和介面都用於派生,則類總是必須放在介面的前面。
Virtual
- 成員欄位和靜態函式都不能宣告為virtual,因為這個概念只對類中的例項函式成員有意義
- 虛方法必須有實現部分,抽象方法沒有提供實現部分
New和base
抽象
- 抽象方法本身也是虛擬的(儘管也不需要提供virtual關鍵字,實際上,如果提供了該關鍵字,就會產生一個語法錯誤)。如果類包含抽象方法,則該類也是抽象的,也必須宣告為抽象的。
- 1. 如果不應建立派生自某個自定義類的類,該自定義類就應密封。給類新增sealed修飾符,就不允許建立該類的子類。密封一個方法,表示不能重寫該方法。
- public、protected和private是邏輯訪問修飾符。internal是一個物理訪問修飾符,其邊界是一個程式集。
- 一般情況下,介面只能包含方法、屬性、索引器和事件的宣告。
- 介面既不能有建構函式(如何構建不能例項化的物件?)也不能有欄位(因為這隱含了某些內部的實現方式)。介面定義也不允許包含運算子過載
- 在介面定義中還不允許宣告成員的修飾符。介面成員總是隱式為public,不能宣告為virtual
- 返回物件的引用。然而,它從不丟擲InvalidCastException異常。相反,如果物件不是所要求的型別,這個運算子就返回null
- is運算子根據條件是否滿足,物件是否使用指定的型別,返回true或false
密封
修飾符
介面
As和is
五、泛型
1.效能
1.
2.安全
1. ArrayList類一樣,如果使用物件,就可以在這個集合中新增任意型別,泛型類List<T>中,泛型型別T定義了允許使用的型別。有了List<int>的定義,就只能把整數型別新增到集合中
3、協變和抗變
對引數和返回值的型別進行轉換
4、泛型方法
5、泛型類
1、預設值
2、約束
3、靜態成員
6、泛型介面
六、陣列
簡單陣列
1. 陣列是引用型別,所以必須給它分配堆上的記憶體。為此,應使用new運算子,指定陣列中元素的型別和數量來初始化陣列的變數
2. 陣列初始化器只能在宣告陣列變數時使用,不能在宣告陣列之後使用。
3. 如果用花括號初始化陣列,則還可以不指定陣列的大小,因為編譯器會自動統計元素的個數:
- 使用花括號可以同時宣告和初始化陣列,編譯器生成的程式碼與前面的例子相同:
鋸齒狀陣列
Array類
- Array類是一個抽象類,所以不能使用建構函式來建立陣列
- 因為陣列是引用型別,所以將一個陣列變數賦予另一個陣列變數,就會得到兩個引用同一陣列的變數。而複製陣列,會使陣列實現ICloneable介面。這個介面定義的Clone()方法會建立陣列的淺表副本。
- Clone()方法會建立一個新陣列,而Copy()方法必須傳遞階數相同且有足夠元素的已有陣列。
- Sort()方法需要陣列中的元素實現IComparable介面。因為簡單型別(如System.String和System.Int32)實現IComparable介面,所以可以對包含這些型別的元素排序。
- 如果對陣列使用自定義類,就必須實現IComparable介面。這個介面只定義了一個方法CompareTo(),如果要比較的物件相等,該方法就返回0。如果該例項應排在引數物件的前面,該方法就返回小於0的值。如果該例項應排在引數物件的後面,該方法就返回大於0的值。
複製
排序
IEnumerator介面
Yield
- yield return語句返回集合的一個元素,並移動到下一個元素上。yield break可停止迭代。
- 陣列合並了相同型別的物件,而元組合並了不同型別的物件
- 不同的泛型Tuple類支援不同數量的元素。例如,Tuple<T1>包含一個元素,Tuple<T1, T2>包含兩個元素,依此類推。
元組
七、運算子
1、checked和unchecked運算子
1. byte資料型別只能包含0~255的數,給byte.MaxValue分配一個位元組,得到255。對於255,位元組中所有可用的8個位都得到設定:11111111。所以遞增這個值會導致溢位,得到0。
2、is運算子
is運算子可以檢查物件是否與特定的型別相容。短語“相容”表示物件或者是該型別,或者派生自該型別
3、as運算子
as運算子用於執行引用型別的顯式型別轉換。如果要轉換的型別與指定的型別相容,轉換就會成功進行;如果型別不相容,as運算子就會返回null值。如下面的程式碼所示,如果object引用實際上不引用string例項,把object引用轉換為string就會返回null:
4、sizeof運算子
5、typeof運算子
typeof運算子返回一個表示特定型別的System.Type物件。例如,typeof(string)返回表示System.String型別的Type物件。在使用反射技術動態地查詢物件的相關資訊時
6、??空合併運算子
如果第一個運算元不是null,整個表示式就等於第一個運算元的值。● 如果第一個運算元是null,整個表示式就等於第二個運算元的值。
7、空值傳播運算子?.
8、型別轉換
1、使用.NET類庫中提供的一些方法。Object類實現了一個ToString()方法,該方法在所有的.NET預定義型別中都進行了重寫,並返回物件的字串表示:
2、
9、相等比較
1.ReferenceEquals()
是一個靜態方法,其測試兩個引用是否指向類的同一個例項,特別是兩個引用是否包含記憶體中的相同地址。作為靜態方法,它不能重寫
3. Equals()虛方法
Microsoft已經在System.ValueType類中過載了例項方法Equals(),以便對值型別進行合適的相等性測試。如果呼叫sA.Equals(sB),其中sA和sB是某個結構的例項,則根據sA和sB是否在其所有的欄位中包含相同的值而返回true或false
4. 靜態的Equals()方法
Equals()的靜態版本與其虛例項版本的作用相同,其區別是靜態版本帶有兩個引數,並對它們進行相等性比較。這個方法可以處理兩個物件中有一個是null的情況;因此,如果一個物件可能是null,這個方法就可以丟擲異常,提供額外的保護。靜態過載版本首先要檢查傳遞給它的引用是否為null。如果它們都是null,就返回true(因為null與null相等)
4. 比較==方法
Microsoft重寫了這個運算子,以比較字串的內容,而不是比較它們的引用。
10、運算子過載
八、委託和事件
委託
Delegate int Calculator(int x,int y)
Calculator c = new Calculator(Add);
Test(Calculator c,X,Y)//把方法當作引數傳遞 ,傳遞入參,返回值相同的同一類方法
{
C(x,y);
}
委託只是一種特殊型別的物件,其特殊之處在於,我們以前定義的所有物件都包含資料,而委託包含的只是一個或多個方法的地址。
多播委託只會返回最後一次註冊的方法的執行結果,其他的方法執行了,但是方法的執行結果無法用變數接到。
委託是一個類,它定義了方法的型別,使得可以將方法當作另一個方法的引數來進行傳遞,這種將方法動態地賦給引數的做法,可以避免在程式中大量使用If-Else(Switch)語句,同時使得程式具有更好的可擴充套件性。
入參和返回值相同即可建立委託,賦值不同的方法
switch轉換為委託
Action<T>
- 泛型Action<T>委託表示引用一個void返回型別的方法
- Action<in T>呼叫帶一個引數的方法,Action<in T1,in T2>呼叫帶兩個引數的方法
Func<T>
Func<T>允許呼叫帶返回型別的方法。與Action<T>類似,Func<T>也定義了不同的變體,至多也可以傳遞16個引數型別和一個返回型別。Func<out TResult>委託型別可以呼叫帶返回型別且無引數的方法,Func<in T, out TResult>呼叫帶一個引數的方法,Func<in T1, in T2, in T3,in T4, out TResult>呼叫帶4個引數的方法。
事件
引數由委託型別定義
釋出器
是一個包含事件和委託定義的物件。事件和委託之間的聯絡也定義在這個物件中。釋出器(publisher)類的物件呼叫這個事件,並通知其他的物件。
這個方法的實現確認委託是否為空,如果不為空,就引發事件
訂閱器
九、字串與正規表示式
1.string常用方法
重複修改給定的字串,效率會很低,它實際上是一個不可變的資料型別,這意味著一旦對字串物件進行了初始化,該字串物件就不能改變了。表面上修改字串內容的方法和運算子實際上是建立一個新字串
2stringbuilder
StringBuilder類有兩個主要的屬性:
● Length指定包含字串的實際長度。
● Capacity指定字串在分配的記憶體中的最大長度。
對字串的修改就在賦予StringBuilder例項的記憶體塊中進行,這就大大提高了追加子字串和替換單個字元的效率。刪除或插入子字串仍然效率低下,因為這需要移動隨後的字串部分
一般而言,使用StringBuilder類執行字串的任何操作,而使用String類儲存字串或顯示最終結果。
不能把StringBuilder強制轉換為String(隱式轉換和顯式轉換都不行)。如果要把StringBuilder的內容輸出為String,唯一的方式就是使用ToString()方法。前面介紹了StringBuilder類,說明了使用它提高效能的一些方式。但要注意,這個類並不總能提高效能。StringBuilder類基本上應在處理多個字串時使用。但如果只是連線兩個字串,使用System.String類會比較好。
3正規表示式
十、集合
大多數集合類都可在System.Collections和System.Collections.Generic名稱空間中找到。泛型集合類位於System.Collections.Generic名稱空間中執行緒安全的集合類位於System.Collections.Concurrent名稱空間中。不可變的集合類在System.Collections.Immutable名稱空間中。
1列表
- 泛型類List<T>中,必須為宣告為列表的值指定型別,ArrayList是一個非泛型列表,它可以將任意Object型別作為其元素。
- 元素新增到列表中後,列表的容量就會擴大,每次都會將列表的容量重新設定為原來的2倍。如果列表的容量改變了,整個集合就要重新分配到一個新的記憶體塊中,為節省時間,如果事先知道列表中元素的個數,就可以用建構函式定義其容量,可以呼叫TrimExcess()方法,去除不需要的容量。但是,因為重新定位需要時間,所以如果元素個數超過了容量的90%, TrimExcess()方法就什麼也不做。
- 使用List<T>類的AddRange()方法,可以一次給集合新增多個元素。因為AddRange()方法的引數是IEnumerable<T>型別的物件,所以也可以傳遞一個陣列
- 刪除
搜尋
排序
- List<T>類可以使用Sort()方法對元素排序。Sort()方法使用快速排序演算法,比較所有的元素,直到整個列表排好序為止。
- 如果傳遞給Compare方法的兩個元素的順序相同,該方法則返回0。如果返回值小於0,說明第一個引數小於第二個引數;如果返回值大於0,則第一個引數大於第二個引數。傳遞null作為引數時,Compare方法並不會丟擲一個NullReferenceException異常。相反,因為null的位置在其他任何元素之前,所以如果第一個引數為null,該方法返回-1,如果第二個引數為null,則返回+1
佇列
棧
連結串列
字典
1. 允許按照某個鍵來訪問元素。字典也稱為對映或雜湊表。字典的主要特性是能根據鍵快速查詢值。也可以自由新增和刪除元素,這有點像List<T>類,但沒有在記憶體中移動後續元素的效能開銷
2.鍵會轉換為一個雜湊。利用雜湊建立一個數字,它將索引和值關聯起來。然後索引包含一個到值的連結。該圖做了簡化處理,因為一個索引項可以關聯多個值,索引可以儲存為一個樹型結構。
3. 用作字典中鍵的型別必須重寫Object類的GetHashCode()方法。只要字典類需要確定元素的位置,它就要呼叫GetHashCode()方法。GetHashCode()方法返回的int由字典用於計算在對應位置放置元素的索引
- 鍵型別還必須實現IEquatable<T>.Equals()方法,或重寫Object類的Equals()方法。因為不同的鍵物件可能返回相同的雜湊程式碼,所以字典使用Equals()方法來比較鍵。字典檢查兩個鍵A和B是否相等,並呼叫A.Equals(B)方法。這表示必須確保下述條件總是成立:如果A.Equals(B)方法返回true,則A.GetHashCode()和B.GetHashCode()方法必
十一、非同步程式設計
當一個方法被呼叫時,呼叫者需要等待該方法執行完畢並返回才能繼續執行,我們稱這個方法是同步方法;當一個方法被呼叫時立即返回,並獲取一個執行緒執行該方法內部的業務,呼叫者不用等待該方法執行完畢,我們稱這個方法為非同步方法。
windows系統是一個多執行緒的作業系統。一個程式至少有一個程序,一個程序至少有一個執行緒。程序是執行緒的容器,一個C#客戶端程式開始於一個單獨的執行緒,CLR(公共語言執行庫)為該程序建立了一個執行緒,該執行緒稱為主執行緒。例如當我們建立一個C#控制檯程式,程式的入口是Main()函式,Main()函式是始於一個主執行緒的。它的功能主要是產生新的執行緒和執行程式
一個程序內可以包括多個應用程式域,也有包括多個執行緒,執行緒也可以穿梭於多個應用程式域當中。但在同一個時刻,執行緒只會處於一個應用程式域內。執行緒也能穿梭於多個上下文當中,進行物件的呼叫。
AppDomain
- 一個程序中可以有多個AppDomain,並且每個之間相互隔離(只保證安全程式碼的隔離,不安全程式碼並不能保證),此可以理解為AppDomain是.net程式中的"程序",在一個AppDomain中建立的物件只屬於本AppDomain,多個AppDomain之間的物件不能相互訪問,除非遵循CLR的一些規則。
- .net程式啟動時在程序中建立一個預設的AppDomain,入口程式碼將執行於此AppDomain,預設應用程式域只有在程序終止時才會被銷燬
每個AppDomain都單獨的載入程式集,這意味著在A應用程式域中載入了的程式集,並不一定在B應用程式域中也被載入了。每個APPDomain有單獨的Loader堆
- 有一種程式集可以被多個AppDomain使用,這種程式集叫做"AppDomain中立"的程式集,比如MSCorLib.dll,該程式集包含了System.Object,System.Int32以及其它的與.net framework密不可分的型別,這個程式集在CLR初始化時會自動載入,JIT會為這些程式集建立一個特殊的Loader堆,並且程式集中的方法被編譯成的原生代碼可被所有AppDomain共享,這種程式集不可被解除安裝只有當程序結束時這種程式集才會被解除安裝。
程序
執行緒是作業系統分配處理器時間的基本單元,在程序中可以有多個執行緒同時執行程式碼。程序之間是相對獨立的,一個程序無法訪問另一個程序的資料(除非利用分散式計算方式),一個程序執行的失敗也不會影響其他程序的執行,Windows系統就是利用程序把工作劃分為多個獨立的區域的。程序可以理解為一個程式的基本邊界。是應用程式的一個執行例程,是應用程式的一次動態執行過程。
執行緒
執行緒的最大並行數量上限是CPU核心的數量,但是,往往電腦執行的執行緒的數量遠大於CPU核心的數量,所以 還是需要CPU時間片的切換。
執行緒(Thread)是程序中的基本執行單元,是作業系統分配CPU時間的基本單位
執行緒主要是由CPU暫存器、呼叫棧和執行緒本地儲存器(Thread Local Storage,TLS)組成的。CPU暫存器主要記錄當前所執行執行緒的狀態,呼叫棧主要用於維護執行緒所呼叫到的記憶體與資料,TLS主要用於存放執行緒的狀態資訊。
async/await
任務
Task是在ThreadPool的基礎上推出的,我們簡單瞭解下ThreadPool。ThreadPool中有若干數量的執行緒,如果有任務需要處理時,會從執行緒池中獲取一個空閒的執行緒來執行任務,任務執行完畢後執行緒不會銷燬,而是被執行緒池回收以供後續任務使用。當執行緒池中所有的執行緒都在忙碌時,又有新任務要處理時,執行緒池才會新建一個執行緒來處理該任務,如果執行緒數量達到設定的最大值,任務會排隊,等待其他任務釋放執行緒後再執行。執行緒池能減少執行緒的建立,節省開銷
Task的阻塞方法(Wait/WaitAll/WaitAny)
Task的延續操作(WhenAny/WhenAll/ContinueWith)
Cpu
快取
L1最靠近CPU核心;L2其次;L3再次。執行速度方面:L1最快、L2次快、L3最慢;容量大小方面:L1最小、L2較大、L3最大。CPU會先在最快的L1中尋找需要的資料,找不到再去找次快的L2,還找不到再去找L3,L3都沒有那就只能去記憶體找了。
L1和L2cache是每個cpu核獨享的,L3cache是多個cpu核共享
超執行緒
CPU 上剩餘的部分,也就是 UnCore 部分Core 的基礎上再做擴充套件,將一個 Core 分裂成多個虛擬核心,即對應兩組ALU
CPU計算速度是納秒級別,記憶體讀寫卻是百納秒,那麼為了充分利用CPU,可以把多項任務的資料都放在快取裡
上下文切換
排程開銷中的最大成分是上下文切換,即CPU從執行一道程序或執行緒切換到執行另一道程序或執行緒的動作,如圖3所示。上下文切換時要進行保護現場和恢復現場,即儲存某道程序被搶佔或阻塞前最後一次執行時的執行環境,和在下一次執行時復現
社會就是應用程式域,我們所住的住宅社群就是上下文的容器,社群的門衛就是上下文的行為,門衛+社群=上下文。而我們就是物件,社群的門衛對於進出社群的陌生人都會問一句:你進來找哪家?找誰?幹什麼的?
而真正的上下文也是幹這個的
String轉字串陣列
string.ToCharArray();
string.Trim()刪除空格 TrimStart() TrimEnd()
string.PadLeft()在左邊新增
string.Split(陣列) 把string轉為string陣列並在指定的位置分隔開
引數陣列
static int get(params int[] i){
return i[0];
}
引用引數
static void show(ref int i)
show(ref a); 必須使用初始化的變數
全域性變數
Static 或const禁止修改變數的值
輸出引數 可以傳入未初始化的引數
public void getValues(out int x, out int y )
{
Console.WriteLine("請輸入第一個值: ");
x = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("請輸入第二個值: ");
y = Convert.ToInt32(Console.ReadLine());
}
n.getValues(out a, out b);
它們傳遞的都是引數的引用
結構函式
不需要static關鍵字
Struct customName{
Public string firstName,lastName;
Public string Name(){
Return firstName+””+ lastName;
}
}
函式的過載
函式的簽名包含函式名及引數,不能僅是返回型別不同
委託
- delegate double ProcessDelegate(double param1,double param2);宣告
指定了一個返回型別和引數列表
- ProcessDelegate process; 委託變數
- process=new processDelegate(Multiply); 初始化為有相同返回型別引數列表的函式引用
- process(param1,param2); 使用委託變數呼叫函式
除錯
常量
靜態
靜態類
1:僅包含靜態變數和靜態方法。
2:無法例項化。
3:不能包含例項建構函式。
4:是密封的。
靜態方法
1.靜態方法不能引用非靜態變數
4靜態方法只能被過載,而不能被重寫,因為靜態方法不屬於類的例項成員;當然也不能是Virtual和abstract型別的
靜態變數
1.只有一個副本,例項化類,不會初始化靜態變數的值。屬於類所有,生命週期和網站運用程式一樣長
2.C# 不支援靜態區域性變數(在方法內部定義靜態變數)。
類可以不顯示例項化,因為內部有一個預設的靜態建構函式,不可過載,當建立類例項或引用任何靜態成員之前,靜態建構函式被自動執行,並且只執行一次。
也可以透過 類名.方法名或變數名 訪問靜態成員
基礎
資料型別
小數不加字尾F預設是double型別
Decimal型別 十進位制 字尾M
數字型別不可為null 布林型別可以為null
內建的 引用型別有:object、dynamic 和 string。
int num;
num = Convert.ToInt32(Console.ReadLine());
函式 Convert.ToInt32() 把使用者輸入的資料轉換為 int 資料型別,因為 Console.ReadLine() 只接受字串格式的資料。
字元常量是括在單引號裡,
字串常量是括在雙引號 "" 裡,或者是括在 @"" 裡。
常量是使用 const 關鍵字來定義的
sizeof() |
返回資料型別的大小。 |
sizeof(int),將返回 4. |
typeof() |
返回 class 的型別。 |
typeof(StreamReader); |
& |
返回變數的地址。 |
&a; 將得到變數的實際地址。 |
* |
變數的指標。 |
*a; 將指向一個變數。 |
? : |
條件表示式 |
如果條件為真 ? 則為 X : 否則為 Y |
is |
判斷物件是否為某一型別。 |
If( Ford is Car) // 檢查 Ford 是否是 Car 類的一個物件。 |
as |
強制轉換,即使轉換失敗也不會丟擲異常。 |
Object obj = new StringReader("Hello"); |
C# 可空型別(Nullable)
int? num1 = null;
int? num2 = 45;
double? num3 = new double?();
Null 合併運算子( ?? )
如果第一個運算元的值為 null,則運算子返回第二個運算元的值,否則返回第一個運算元的值
double? num1 = null;
double? num2 = 3.14157;
double num3;
num3 = num1 ?? 5.34;
foreach迴圈
foreach (int j in n )
{
int i = j-100;
Console.WriteLine("Element[{0}]
= {1}", i, j);
}
迴圈的終止
string常用方法
public static int
Compare( string strA, string strB )
比較兩個指定的 string 物件,並返回一個表示它們在排列順序中相對位置的整數。該方法區分大小寫
public static string
Concat( string str0, string str1 )
連線兩個 string 物件。
public bool Contains( string value )
返回一個表示指定 string 物件是否出現在字串中的值。
public static string
Copy( string str )
建立一個與指定字串具有相同值的新的
String 物件。
public bool EndsWith(
string value )
判斷 string 物件的結尾是否匹配指定的字串。
public bool Equals( string value )
判斷當前的 string 物件是否與指定的 string 物件具有相同的值。
public static string Format( string format,
Object arg0 )
把指定字串中一個或多個格式項替換為指定物件的字串表示形式。
public int IndexOf( string value )
返回指定字串在該例項中第一次出現的索引,索引從 0 開始。
public string Insert( int startIndex, string
value )
返回一個新的字串,其中,指定的字串被插入在當前 string 物件的指定索引位置。
public static string Join( string separator, string[] value )
連線一個字串陣列中的所有元素,使用指定的分隔符分隔每個元素。
public string Replace( string oldValue,
string newValue )
把當前 string 物件中,所有指定的字串替換為另一個指定的字串,並返回新的字串。
public string[] Split( params char[] separator )
返回一個字串陣列,包含當前的 string 物件中的子字串,子字串是使用指定的 Unicode 字元陣列中的元素進行分隔的
public string ToLower()
把字串轉換為小寫並返回。
public string Trim()
移除當前 String 物件中的所有前導空白字元和後置空白字元。
列舉
結構體
- · 結構可帶有方法、欄位、索引、屬性、運算子方法和事件。
- · 結構可定義建構函式,但不能定義解構函式。但是,您不能為結構定義無參建構函式。無參建構函式(預設)是自動定義的,且不能被改變。
- · 與類不同,結構不能繼承其他的結構或類。
- · 結構不能作為其他結構或類的基礎結構。
- · 結構可實現一個或多個介面。
- · 結構成員不能指定為 abstract、virtual 或 protected。
- · 當您使用 New 運算子建立一個結構物件時,會呼叫適當的建構函式來建立結構。與類不同,結構可以不使用 New 運算子即可被例項化。
- · 如果不使用 New 運算子,只有在所有的欄位都被初始化之後,欄位才被賦值,物件才被使用
- 類是引用型別,結構是值型別。
- 結構不支援繼承。
- 結構不能宣告預設的建構函式。
- 1、結構是值型別,它在棧中分配空間;而類是引用型別,它在堆中分配空間,棧中儲存的只是引用。
- 2、結構型別直接儲存成員資料,讓其他類的資料位於堆中,位於棧中的變數儲存的是指向堆中資料物件的引用。
C# 中的簡單型別,如int、double、bool等都是結構型別。如果需要的話,甚至可以使用結構型別結合運算子運算過載,再為 C# 語言建立出一種新的值型別來。
由於結構是值型別,並且直接儲存資料,因此在一個物件的主要成員為資料且資料量不大的情況下,使用結構會帶來更好的效能。
因為結構是值型別,因此在為結構分配記憶體,或者當結構超出了作用域被刪除時,效能會非常好,因為他們將內聯或者儲存在堆疊中。當把一個結構型別的變數賦值給另一個結構時,對效能的影響取決於結構的大小,如果結構的資料成員非常多而且複雜,就會造成損失,接下來使用一段程式碼來說明這個問題。
結構和類的適用場合分析:
- 1、當堆疊的空間很有限,且有大量的邏輯物件時,建立類要比建立結構好一些;
- 2、對於點、矩形和顏色這樣的輕量物件,假如要宣告一個含有許多個顏色物件的陣列,則CLR需要為每個物件分配記憶體,在這種情況下,使用結構的成本較低;
- 3、在表現抽象和多級別的物件層次時,類是最好的選擇,因為結構不支援繼承。
- 4、大多數情況下,目標型別只是含有一些資料,或者以資料為主。
enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
陣列
運算子過載
public static Box operator+ (Box b, Box c)
{
Box box = new Box();
box.length = b.length + c.length;
box.breadth = b.breadth + c.breadth;
box.height = b.height + c.height;
return box;
}
賦值運算子不能被過載
函式過載
同名不同引數個數、引數型別
返回值不同不算
前處理器指令
#define |
它用於定義一系列成為符號的字元。 |
#undef |
它用於取消定義符號。 |
#if |
它用於測試符號是否為真。 |
#else |
它用於建立複合條件指令,與 #if 一起使用。 |
#elif |
它用於建立複合條件指令。 |
#endif |
指定一個條件指令的結束。 |
#line |
它可以讓您修改編譯器的行數以及(可選地)輸出錯誤和警告的檔名。 |
#error |
它允許從程式碼的指定位置生成一個錯誤。 |
#warning |
它允許從程式碼的指定位置生成一級警告。 |
#region |
它可以讓您在使用 Visual Studio Code Editor 的大綱特性時,指定一個可展開或摺疊的程式碼塊。 |
#endregion |
它標識著 #region 塊的結束。 |
檔案操作
FileMode |
FileMode 列舉定義了各種開啟檔案的方法。FileMode 列舉的成員有:
|
FileAccess |
FileAccess 列舉的成員有:Read、ReadWrite 和 Write。 |
FileShare |
FileShare 列舉的成員有:
|
FileStream F = new FileStream("test.dat",
FileMode.OpenOrCreate, FileAccess.ReadWrite);
for (int i = 1; i <= 20; i++)
{
F.WriteByte((byte)i);
}
F.Position = 0;
for (int i = 0; i <= 20; i++)
{
Console.Write(F.ReadByte() + " ");
}
F.Close();
型別轉換
在C#中提供的很好的型別轉換方式總結為:
Object => 已知引用型別——使用as運算子完成;
Object => 已知值型別——先使用is運算子來進行判斷,再用型別強轉換方式進行轉換;
已知引用型別之間轉換——首先需要相應型別提供轉換函式,再用型別強轉換方式進行轉換;
已知值型別之間轉換——最好使用系統提供的Conver類所涉及的靜態方法。
as運算子只執行引用轉換和裝箱轉換。as運算子無法執行其它轉換,如果使用者定義的轉換,這類轉換應使用強制轉換表示式來執行。
as運算子的工作方式與強制型別轉換一樣,只是它永遠不會丟擲一個異常。相反,如果物件不能轉換,結果就是null。
Is
Is:檢查物件是否與給定的型別相容。例如,下面的程式碼可以確定MyObject型別的一個例項,或者物件是否從MyObject派生的一個型別: