一.const與readonly的爭議
你一定寫過const,也一定用過readonly,但說起兩者的區別,並說出何時用const,何時用readonly,你是否能清晰有條理地說出個一二三? const與readonly之所以有如此爭議,是因為彼此都存在"不可改變"這一特性,對於二者而言,我們需要關心的是,什麼時候開始不可變?什麼是不可改變的?這就引出了我們下面要討論的話題.二.什麼時候開始不可變?
我們先拋出結論. const在程序運行的任何時候都是不可變的,無論什麼時候開始,什麼時候結束,它的值是固化在代碼中的,我們稱之為編譯期常量; readonly在某個具體實例第一次初始時指定它的值(出了構造函數後,對於這個實例而言,它就不能改變)或者是作為靜態成員在運行時加載它的值,我們稱之為運行時常量. 我們先談const: 1.const由於其值從不變化,我們稱之為常量,常量總是靜態的,因此const是天然static的,我們不能再用static修飾const.如下圖所示:1 public class MathHelper 2 { 3 public const float PI= 3.14159F; 4 }調用:
1 static void Main(string[] args) 2 { 3 float pi= MathHelper.PI; 4 }我們查看生成的IL代碼,如下:
1 public class MathHelper 2 { 3 public readonly float PI; 4 }訪問:
1 public class MathHelper 2 { 3 public MathHelper() 4 { 5 this.PI = 3.15F; 6 this.PI = 3.14F; 7 } 8 public readonly float PI; 9 }調用代碼:
1 static void Main(string[] args) 2 { 3 MathHelper m = new MathHelper(); 4 Console.WriteLine(m.PI); 5 }輸出結果:
1 public class MathHelper 2 { 3 public MathHelper(float pi) 4 { 5 this.PI = pi; 6 } 7 public readonly float PI; 8 }調用如下:
1 static void Main(string[] args) 2 { 3 MathHelper m1 = new MathHelper(3.14F); 4 Console.WriteLine(m1.PI); 5 6 MathHelper m2 = new MathHelper(3.15F); 7 Console.WriteLine(m2.PI); 8 9 Console.Read(); 10 }輸出結果:
1 public class MathHelper 2 { 3 public readonly float PI=3.15F; 4 }其實,這是一種內聯寫法,是C#的一種語法糖,只是一種語法上的簡化,實際它們也是在構造方法中進行初始化的.C#允許使用這種簡化的內聯初始化語法來初始化類的常量、read/write字段和readonly字段。 5.readonly賦值的第二種情況:如果我用static修飾readonly會發生什麼? 前面講const時,我們說過const是靜態的,這種靜態不可以顯式指定,因此在const前加static會導致編譯器編譯失敗.那我們把static修飾readonly會發生什麼樣的結果? 首先,我們確定,靜態的是屬於類的,此時的readonly我們不能通過構造函數來指定.
1 public class MathHelper 2 { 3 public static readonly float PI=3.14F; 4 }調用:
1 static void Main(string[] args) 2 { 3 Console.WriteLine(MathHelper.PI); 4 Console.Read(); 5 }結果與我們預期的一致:
三.什麼是不可變的?
前面我們花了大量的篇幅說明const與readonly的變量什麼時候才開始不可變,有的從一開始就不可變,有的是第一次加載的時候不可變,有的是出了構造函數後不可變,但是我們有一個十分關鍵的問題沒有弄清楚:什麼東西不可變?也許童鞋們很疑惑,值不可變呗!這話不完全對. 要想理解這個問題,我們需要明白const與readonly修飾的對象,也就是我們不變的內容. const可以修飾基元類型:Boolean、Char、Byte、SByte、Int16、UInt16、Int32、UInt32、Int64、UInt64、Single、Double、Decimal和String。也可以修改類class,但要把值設置為null。不可以修飾struct,因為struct是值類型,不可以為null. 對於基元類型來說,值是存儲在棧上的,因此我們可以認為不變的是值本身,這裡string是一個特殊的引用類型,這裡它也存在值類型的特征,因此也可以認為它不變的是值本身. 對於readonly而言,readonly可以修飾任何類型.對於基元類型而言,我們可以認為它與const無異,但是對於引用類型,我們需要謹慎對待,不可想當然,下面我們通過實驗來得出結論:1 public class Alphabet 2 { 3 public static readonly Char[] Letters = new Char[] {'A','B','C','D','E','F' }; 4 }調用:
1 static void Main(string[] args) 2 { 3 Alphabet.Letters[0] = 'a'; 4 Alphabet.Letters[0] = 'b'; 5 Alphabet.Letters[0] = 'c'; 6 Alphabet.Letters[0] = 'd'; 7 Alphabet.Letters[0] = 'e'; 8 Alphabet.Letters[0] = 'f'; 9 Console.WriteLine(Alphabet.Letters.Length); 10 Console.Read(); 11 }可賦值!!! 輸出結果如下:
四:總結
到此,我們的const與readonly的庖丁解牛式的解析也就告一段落了,說了這麼多,我們其實也就是想說明以下2點: 1.const任何時候都不變,比readonly快,但不能解決跨版本程序集問題,readonly靜態時在第一次JIT編譯後不變,實例時在出了實例的構造函數後不可變. 2.const修飾基元類型,不變的是值;readonly修飾值類型時,其值不變,修改引用類型時,其引用不變. 以上.參考文檔:
《CLR via C#(第4版)》 《Effice C#:改進C#代碼的50個行之有效的辦法》 《編寫高質量代碼:改善C#程序的157個建議》