口袋筆記(一):C#編程要點,
前言
口袋筆記系列著重於記錄日常的小技術點,點與點之間雖然可能聯系不大。但足夠小,也便於熟悉並運用到日常的開發周期中,產生實際的效益。記住是第一步,不是最終目的。最終目的是在了解原理下開發出兼顧靈活性、可擴展性、可移植性的高效益程序。本系列隨以C#的小知識點開篇,但後續不局限於某種特定語言。來源於手中日常摘錄的資料和書籍,算是對看過的東西的總結,部分注有閱讀心得,也有部分只提出大綱或結論。(備注:本篇文章中大部分要點需要有實際的開發經驗,有助於閱讀理解。)
目錄
- const和readonly
- is、as
- 條件編譯#if #endif和Conditional特性
- 等同性判斷
- GetHashCode()陷阱
- 委托
- 資源管理(GC、終結器、IDisposable、using)
- 創建第一個實例所進行的操作順序
- 編譯的生命周期
一、const和readonly
當編譯期常量const被編譯成IL時,它就已經被替換成所代表的字面數值。所以更改一個公有的編譯期常量的值,需要重新編譯所有引用到該常量的代碼以保證所有代碼使用的是最新的常量值。
相反,運行時常量被編譯成IL時引用的是readonly的變量,而不是變量的值,只需要重新編譯更改了常量值的代碼,就能實現對其它已經發布的代碼在二進制層次上的兼容。
二、is、as
分兩種情況:
轉換的目標類型是引用類型:使用is測試能否轉換成功,然後再用as進行轉換(as在轉換對象為null時會返回null)。as和is不會執行用戶自定義的轉換,只有當運行時類型是目標類型或者是目標的派生類型,才會轉換成功。執行用戶自定義的轉換可以用強制轉換。
轉換的目標類型是值類型:不能使用as,可以使用強制轉換。
三、條件編譯#if #endif和Conditional特性
兩者都適用於日常調試。
#if 可以穿插在函數中添加條件性代碼,但使用Conditional特性可以限制在函數層面上將條件性的代碼(如DEBUG)分離出來,保證代碼的良好結構。
四、等同性判斷
(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()方法。
五、GetHashCode()陷阱
在引用類型(System.Object)中:GetHashCode()能正常工作,雖然它不必然會產生一個高效的分布。
在值類型(System.ValueType)中:只有在struct的第一個字段是只讀的情況下,GetHashCode()才能正常工作,只有當第一個字段包含的值有著相對隨機的分布,GetHashCode()才會產生一個比較高效的散列碼。
GetHashCode覆寫規則
六、委托
內建的委托形式
- Predicate<T>:表示一個提供布爾型返回值的函數;
- Action<T1,T2>:接受任意參數目的參數;
- Func<T1,T2,ResultT>:接受零到多個參數,並返回單一結果;
.NET的委托都是多播委托(multicast delegate),多播委托將會把所有添加到該委托中的所有目標函數組合成一個單一的調用。有兩點需要注意:
解決方法:自己遍歷調用列表,調用委托鏈上的每個目標函數
委托應用於回調
接受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;}
}
事件相當於一個委托集合,可以添加多個同類型委托(通過+=);
委托可以添加多個同類型方法(通過構造函數或+=);
七、資源管理
托管堆上的內存由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()實現銷毀非托管資源的標准銷毀模式
(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());
}
八、創建第一個實例所進行的操作順序
創建某個類型的第一個實例時所進行的操作順序,創建同樣類型的第二個以及以後的實例將從第5步開始執行
九、編譯的生命周期
相關資料:
- 《C#高效編程:改進C#代碼的50個行之有效的方法》(第2版)
作者:B.it
出處:http://www.cnblogs.com/ImBit/p/5484920.html
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。