1.equal()和運算符==的區別
由于C#中有值類型和引用類型,那么相等也分為值相等和引用相等。先來看一個值類型簡單的例子,順便也寫了string類型的比較。
static void Main(string[] args){int n1 = 1;int n2 = 1;Console.WriteLine(n1==n2);Console.WriteLine(n1.Equals(n2));string str1 = "test";string str2 = "test";Console.WriteLine(str1==str2);Console.WriteLine(str1.Equals(str2));Console.ReadKey();//結果是4個 True}
前2個結果為true我可以理解,但是后2個為true我有點懷疑。一直記得不斷有人說string是特殊的引用類型,那這里應該是為str1和str2都分配了內存并str1和str2指向各自的內存,但是結果卻是true。我感覺很有可能是因為string類的“特殊”,才發現自己一直都沒有理解string類的特殊。百度之后發現原來是.NET做的優化,由于str1和str2內容相同,為了節省內存故讓str1和str2指向同一個內存區域,這樣就不用為str2開辟空間了。這種技術是字符串駐留技術,當CLR初始化時,會創建一個內部的散列表,鍵為字符串,值Wie指向堆中字符串的引用,JIT編譯方法時,添加str1和“test”到空的散列表里了,str2賦值時會先看散列表里有沒有相等的,有的就直接指向相等的值的引用。編程中當把string作為參數傳遞時不會改變原有string對象的值,因為每次賦值會另外開辟內存,這就是string引用類型的特殊之處。
對于引用類型,==比較的是兩個引用是不是指向同一個內存地址,equal()方法比較的是兩個對象指向的內存空間內容是否相同。下面是關于引用類型的代碼。
class Program{static void Main(string[] args){//前面已提過這里str1和str2指向同一個引用,故obj1和obj2表示的地址是相同的string str1 = "test";string str2 = "test";object obj1 = str1;object obj2 = str2;Console.WriteLine(obj1 == obj2); //TrueConsole.WriteLine(obj1.Equals(obj2)); //True//這種情況的賦值內存沒有做優化,故obj3和obj4表示的是不一樣的地址string str3 = new string(new char[] { 't', 'e', 's', 't' });string str4 = new string(new char[] { 't', 'e', 's', 't' });object obj3 = str3;object obj4 = str4;Console.WriteLine(obj3 == obj4); //FalseConsole.WriteLine(obj3.Equals(obj4)); //True//當用new開辟新的空間創建對象時,people1和people2肯定是指向了不同的內存地址//而且這是2個不同的對象,我們不能只看它們有相同的方法相同的字段,還要看它們的標識等環境變量,//對于people3和peop4來說則是指向了同一塊內存區域People people1 = new People("小方");People people2 = new People("小方");Console.WriteLine(people1 == people2); //FalseConsole.WriteLine(people1.Equals(people2)); //FalsePeople people3 = new People("小白");People people4 = people3;Console.WriteLine(people3 == people4); //TrueConsole.WriteLine(people3.Equals(people4)); //True Console.ReadKey();}}class People{string name = null;public string Name{get { return name; }set { name = value; }}public People(string strName){name = strName;}}
引用類型的變量是存在棧中,但指向的是堆中的地址。==操作符比較的是2個變量的值是否相等,那對于引用類型也就是比較它們表示的地址是否相同。equal表示的是2個變量指向的堆中的內容是否相等。
2.思考:為什么要封裝字段
在上面的例子中,已經形成習慣將字段封裝成屬性。我發現學習過程中大家總是這么寫,我有時候會想一下為什么要這樣寫,但可能還沒有做過實際的項目開發(沒有因為不這樣寫被坑過),所有我一直沒有想到最本質的原因。因為經常說為了保護數據的安全性不讓直接讀和寫,可是常常看到屬性中是既有set又有get的,那這樣還不是直接讀和寫,這樣有什么區別呢?今天既然又發現這個問題,不能還不解決了。查閱了前輩的經驗后,我發現有2點說到本質了:
1:安全性,也就是我可以在屬性中進行判斷,比如age,我可以在set的時候加一個if判斷范圍在0到150,雖然也可以在外部判斷,但這樣寫是一定不會出錯的!
2:當出現修改時,我們可以在屬性中進行修改,而不必修改整個程序。舉個例子,比如業務需求改變為我們給一個變量賦值,get時不是輸出原來的值,而是輸出這個值乘以2,如果該字段是public,整個程序用到該字段的地方都要修改,然而如果有屬性那就很好辦了,只需在get時乘以2就可以了。
3.Dispose()、Close()、Finalize()的區別
Close()和Dispose()差不多,只是因為Close這個詞更加容易理解,所以在Close()方法內部調用了Dispose()方法。Dispose方法在內部是去調用一個virtual的Dispose(bool)函數去釋放資源。具有Dispose()方法的類是實現了IDisposable接口的,很多類實現了IDisposable接口,但是只提供Close(),而不對外提供Dispose()。原因是這些類是顯示實現接口,這樣的話實現類對象將無法調用Dispose(),比如ClassA實現接口IDisposable接口,如果要調用Dispose方法則只能調用((IDisposable)new ClassA()).Dispose()。這樣做的目的也就是提供易于理解的Close()。例外有時候調用Close后我們還可以復活對象,而Dispose一旦被調用就會實實在在的釋放資源。
其實.NET最基本的釋放資源方法是Finalize和Dispose這2個方法,Finalize是用于釋放非托管資源的,Dispose可以釋放所有資源,即可釋放托管資源,又可以釋放非托管資源。我們程序員是無法顯示調用Finalize方法的,這個方法當我們使用析構函數時才能被調用,但是程序員并不會知道什么時候會調用析構函數,這將由垃圾回收器控制。只有當垃圾回收器認為對象符合析構的時候才會調用,程序退出時也會調用析構函數。為了提升性能,我們最好不要使用空的析構函數,因為如果類包括析構函數,則Finalize隊列中則會創建一個成員選項,GC處理這個隊列時如果選項內容為空那只會導致不必要的性能。
寫到這里我心里有2個疑問,上面提到建議我們不要使用空的析構函數,可以理解是因為要提升性能,可是我寫析構函數不就是為了調用Finalize方法釋放資源嗎?還有既然Dispose方法可以釋放所有資源,何必還要寫一個Finalize方法呢?直到敲了前輩們寫的代碼才有了答案。
public class People : IDisposable
{//前面我們說了析構函數實際上是重寫了 System.Object 中的虛方法 Finalize, 默認情況下,一個類是沒有析構函數的,也就是說,對象被垃圾回收時不會被調用Finalize方法 ~People(){// 必須以Dispose(false)方式調用,以false告訴Dispose(bool disposing)函數是垃圾回收器在調用Finalize時調用的 Dispose(false);}// 無法被客戶直接調用 // 如果 disposing 是 true, 那么這個方法是被客戶直接調用的,那么托管的,和非托管的資源都可以釋放 // 如果 disposing 是 false, 那么函數是從垃圾回收器在調用Finalize時調用的,此時會釋放托管資源protected virtual void Dispose(bool disposing){// 那么這個方法是被客戶直接調用的,那么托管的,和非托管的資源都可以釋放 if (disposing){// 釋放 托管資源 //......
}//釋放非托管資源 //......// 那么這個方法是被客戶直接調用的,告訴垃圾回收器從Finalization隊列中清除自己,從而阻止垃圾回收器調用Finalize方法. if (disposing)GC.SuppressFinalize(this);}//可以被客戶直接調用 public void Dispose(){Dispose(true);}
}
從上面的例子中可以看出Finalize和Dispose釋放了什么資源,而且如果是Dispose方式釋放資源后,還會調用GC.SuppressFinalize(this),這個方法會告訴GC沒必要再調用析構函數了,因為已經使用Dispose方法釋放資源了。這說明析構函數只是作為資源釋放的一種補救措施,同樣的我們可以在析構函數中寫釋放非托管資源的代碼,最后再調用Finalize方法以保證資源被完全釋放,這樣就萬無一失了!
?聲明:本文原創發表于博客園,作者為方小白,如有錯誤歡迎指出?。本文未經作者許可不許轉載,否則視為侵權。