基礎筆記(一):C#編程要點

前言

      來源於手中平常摘錄的資料和書籍,算是對看過的東西的總結,部分注有閱讀心得,也有部分只提出大綱或結論。(備註:本篇文章中大部分要點須要有實際的開發經驗,有助於閱讀理解。)html

 
 

目錄

 
 

1、const和readonly

當編譯期常量const被編譯成IL時,它就已經被替換成所表明的字面數值。因此更改一個公有的編譯期常量的值,須要從新編譯全部引用到該常量的代碼以保證全部代碼使用的是最新的常量值。編程

相反,運行時常量被編譯成IL時引用的是readonly的變量,而不是變量的值,只須要從新編譯更改了常量值的代碼,就能實現對其它已經發布的代碼在二進制層次上的兼容。
 
 

2、is、as

分兩種狀況:數組

轉換的目標類型是引用類型:使用is測試可否轉換成功,而後再用as進行轉換(as在轉換對象爲null時會返回null)。as和is不會執行用戶自定義的轉換,只有當運行時類型是目標類型或者是目標的派生類型,纔會轉換成功。執行用戶自定義的轉換能夠用強制轉換。
轉換的目標類型是值類型:不能使用as,可使用強制轉換。
 
 

3、條件編譯#if #endif和Conditional特性

二者都適用於平常調試。
#if 能夠穿插在函數中添加條件性代碼,但使用Conditional特性能夠限制在函數層面上將條件性的代碼(如DEBUG)分離出來,保證代碼的良好結構。
 
 

4、等同性判斷

(1)等同性在數學方面的幾個要點:
自反(reflexive):表示任何對象都和其自身相等,不管a是什麼類型,a==a都應該返回true;
對稱(symmetric):意味着等同性判斷時的順序是可有可無的,若a==b返回true,那麼b==a也必然返回true;
可傳遞(transitive):含義是若a==b且b==c都返回true,那麼a==c也必然返回true;
值相等(對應於值類型):若是兩個值類型的變量類型相同,且包含一樣的內容,則「值相等」;
引用相等(對應於引用類型):若是兩個引用類型的變量指向的是同一個對象,則「引用相等」;
 
(2)C#中等同性判斷的四個方法
public static bool ReferenceEquals(object left,object right)
不管比較的是值類型仍是引用類型,該方法判斷的依據都是對象標識。因此用來比較兩個值類型,結果永遠都是false,其緣由在於裝箱。在建立本身的類時,幾乎不須要覆寫。
 
public static bool Equals(object left,object right)
當不知道兩個變量的運行時類型時,使用該方法進行判斷。其內部實現先引用ReferenceEquals進行判斷,再拿left和right與null判斷,最後使用left.Equals(right)判斷。在建立本身的類時,幾乎不須要覆寫。
 
public virtual bool Equals(objcet rigth)
有兩種狀況:
對於引用類型System.Object:使用對象標識做爲判斷,即比較兩個對象是否「引用相等」,默認實現與ReferenceEquals徹底一致。新建引用類型時無需覆寫。
對於值類型System.ValueType:覆寫了System.Object中的該方法,實現了判斷是否」值相等「。由於沒法得知當前具體的值類型,因此使用了反射,效率較低。建議在新建值類型時覆寫該方法,覆寫的同時也要覆寫GetHashCode()方法。
 
public static bool operator==(MyClass left,MyClass right)
引用類型System.Object:使用對象標識做爲判斷。
值類型System.ValueType:覆寫了System.Object中的該方法,由於沒法得知當前具體的值類型,因此使用了反射,效率較低。建議在新建值類型時覆寫該方法,覆寫的同時也要覆寫GetHashCode()方法。
 
 

5、GetHashCode()陷阱

在引用類型(System.Object)中:GetHashCode()能正常工做,雖然它沒必要然會產生一個高效的分佈。
在值類型(System.ValueType)中:只有在struct的第一個字段是隻讀的狀況下,GetHashCode()才能正常工做,只有當第一個字段包含的值有着相對隨機的分佈,GetHashCode()纔會產生一個比較高效的散列碼。
 
GetHashCode覆寫規則
  1. 若是兩個對象相等(由operator==判斷),那麼它們必須生成相同散列碼。不然,這樣的散列碼將沒法用來查找容器中的對象。System.Object:知足此規則;System.ValueType:並不是全部參與等同性判斷的屬性都會用來進行散列碼計算,而是返回struct類型中定義的第一個字段的散列碼做爲當前值類型的散列碼,因此能夠認定等同性判斷相等的散列碼確定相等(反過來,散列碼相等同性判斷不必定相等)。知足此規則。
  2. 對於任何一個對象A,A.GetHashCode()必須保持不變。無論在A上調用什麼方法,A.GetHashCode()都必然老是返回同一個值。這樣能夠確保放在「桶」中的對象老是位於正確的「桶」中。System.Object:知足此規則;System.ValueType:struct中的第一個字段是一個常量字段,不會在生存期發生改變(稱爲不可變的值類型),知足此規則。
  3. 對於全部的輸入,散列函數應該在全部整數中按照隨機分佈生成散列碼。這樣,散列容器才能獲得足夠的效率提高。System.Object:系統每建立一個對象時會指派一個惟一的對象鍵(一個整數值),從1開始,每建立一個任意類型的新對象,鍵值會隨之增加,雖然System.Object.GetHashCode()能正常工做,但由於不是隨機分佈,效率不高,因此沒有知足此規則,新建引用類型時建議覆寫GetHashCode();System.ValueType:依賴於第一個字段的使用方式,若是取任意值,GetHashCode()能夠產生比較均勻的分佈,但若是取相同的值則沒有此知足規則。
 
 

6、委託

內建的委託形式
  • Predicate<T>:表示一個提供布爾型返回值的函數;
  •  Action<T1,T2>:接受任意參數目的參數;
  •  Func<T1,T2,ResultT>:接受零到多個參數,並返回單一結果;
.NET的委託都是多播委託(multicast delegate),多播委託將會把全部添加到該委託中的全部目標函數組合成一個單一的調用。有兩點須要注意:
  1. 在多播委託調用過程當中,每一個目標函數會被依次調用。委託對象自己不會捕獲異常。所以,任何目標拋出的異常都會結束委託鏈的調用,若是有委託調用出現異常,那麼這種方式不能保證安全;
  2. 整個調用的返回值將爲最後一個函數調用的返回值,前面函數的返回值將會被忽略;
解決方法:本身遍歷調用列表,調用委託鏈上的每一個目標函數
委託應用於回調
接受Predicate<>、Action<>、Func<>爲參數的方法,如List<T>.Find(Predicate<T> p);有時傳入lambda表達式,編譯器會把lambda表達式轉換成方法,而後建立一個委託,指向該方法,而後調用委託實現回調。
委託應用於事件

.NET的事件模式就是觀察者模式
public event EventHandler<LoggerEventArgs> Log;
編譯器會自動建立相似下面的代碼,根據須要能夠本身編寫
private EventHander<LoggerEventArgs> log;
public event  EventHander<LoggerEventArgs> Log
{
  add{log=log+value;}
  remove{log=log-value;}
}
事件至關於一個委託集合,能夠添加多個同類型委託(經過+=);
安全

委託能夠添加多個同類型方法(經過構造函數或+=);
 
 

7、資源管理

託管堆上的內存由GC(Garbage Collector 垃圾收集器,CLR中包含GC)進行管理,其它資源由開發者負責。
.NET 提供兩種管理非託管資源生命週期的機制:終結器(finalizer,由GC調用,調用發生在對象成爲垃圾以後的某個時間,時間不可預料)和IDisposable接口。
 
(1)GC
GC可以判斷某個實體目前是否依舊被應用程序的活動對象所引用,對於那些沒有被活動對象直接或間接引用的實體,GC會將其判斷爲垃圾。
GC會在每次運行時壓縮託管堆,壓縮託管堆可以將當前仍舊使用的對象放在連續的內存中,所以空餘空間也是一塊連續的內存。
 
 
(2)終結器
終結器只是一種防護手段,僅僅可以保證給定類型的對象所分配的非託管資源最終被釋放。GC會把須要執行終結的對象放在專門的隊列中,而後讓另外一個線程來執行這些對象的終結器。這樣,GC能夠繼續執行其當前的工做,在內存中移除垃圾對象,而在下一次的GC調用中才會從內存中移除這些已被終結的對象。能夠看到,須要調用終結器的對象將在內存中多停留一個GC週期的時間(實際狀況會比這個更復雜一點,詳情請查看下面「代」的概念),因此應該儘可能少讓代碼的邏輯使用到終結器。
GC爲了優化執行,引入了「代」(generation)的概念。能夠快速地找到那些更有多是垃圾的對象。自上一次垃圾收集以來,新建立的對象屬於第0代對象。若某個對象在經歷過一次垃圾收集以後仍舊存活,那麼將成爲第1代對象。兩次及兩次以上垃圾收集後仍沒有被銷燬的對象就變成了第2代對象。這樣能將局部變量和應用程序生命週期一直使用的對象分開對待。第0代大多屬於局部變量。而成員變量和全局變量則會更快地成爲第1代對象,直至第2代。GC將經過減小檢查第1代和第2代對象的次數來優化執行過程。在每一個週期中,GC都會檢查第0代對象。通常來講,大概10個週期的GC中,會有一次去同時檢查第0代和第1代對象。大概100個週期的GC中,會有一次同時檢查全部對象。能夠看到一個須要總結的對象可能會比普通對象多停留9個GC週期。而如果再次GC的時候仍沒有完成終結炒做,那麼該對象將繼續被提高爲第2代。對於第2代的對象,每每須要100次以上的GC週期纔會有機會被清除。爲了不這個性能問題,建議使用IDisposable接口。
 
(3)Dispose()和Close()
使用了非系統資源的類型會自動在終結器中調用Dispose(),以便在使用者忘記的時候仍保證能正常釋放資源,但這些資源會在內存中停留更長時間,因此最好的方案仍是由使用者本身顯示地使用IDisposable接口的Dispose()來釋放。Dispose()並非將對象從內存中移除,而只是讓對象釋放掉其中的非託管資源。
Dispose()和Close()的區別(Dispose()比Close()要好一些)
Close:清理資源,對象已經不須要被終結,但通常沒有調用GC.SuppressFinalize(),因此對象仍舊在終結隊列中。
Dispose:清理資源,調用GC.SuppressFinalize()告知GC該對象再也不須要被終結
使用IDisposable.Dispose()實現銷燬非託管資源的標準銷燬模式
  1. 釋放全部非託管資源;
  2. 釋放全部託管資源,包括釋放事件監聽程序;
  3. 設定一個狀態標識,表示該對象已經被銷燬。如果在銷燬後再次對用對象的公有方法,那麼應該拋出ObjectDisposed異常;
  4. 調用GC.SuppressFinalize(this),跳過終結操做;

 

(4)using
using語句能以最簡單的方式保證用戶的對象能夠正常銷燬,即便對象在調用操做時出現異常。當有多個對象須要銷燬時,可使用多個using塊或一個try/finally塊。
using(){}=try{}finally{xxx.Dispose();}
下面例子能保證當obj不爲null時正確清理到對象,當obj爲null時,using(null)也不會報錯,但不會作任何清理工做。
object obj=Factory.CreateInstance();
using(obj as IDisposable)
{
 Console.Write(obj.ToString());
}
 
 

8、建立第一個實例所進行的操做順序

建立某個類型的第一個實例時所進行的操做順序,建立一樣類型的第二個以及之後的實例將從第5步開始執行
  1. 靜態變量設置爲0;
  2. 執行靜態變量初始化器;
  3. 執行基類的靜態構造函數;
  4. 執行靜態構造函數;
  5. 實例變量設置爲0;
  6. 執行實例變量初始化器;
  7. 執行基類中合適的實例構造函數;
  8. 執行實例構造函數;
 

 9、編譯的生命週期

 
 
 
 

相關資料:

  • 《C#高效編程:改進C#代碼的50個行之有效的方法》(第2版)

 

做者:B.it函數

 

技術收錄網站:核心技術(http://www.coretn.cn)性能

出處:http://www.cnblogs.com/ImBit/p/5484920.html                                           
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。
測試

 
相關文章
相關標籤/搜索