Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得
C# 提供了兩種不同型式的常數;分別為編譯階段常數與執行階段常數。作者再次使用了"prefer"而非"always",表示兩者各有其使用時機。
// 編譯階段常數
public const int Millennium = 2000;
// 執行階段常數
public static readonly int ThisYear = 2017;
兩者間有以下幾種不同:
1. const 可以在方法內部宣告,而 readonly 常數則不行。
public void M1( )
{
// 編譯成功
const int x = 10;
// 編譯失敗
static readonly int y = 10;
}
2. const 編譯後的 IL Code 是取值,而 readonly 常數是取變數。
if( myDateTime.Year == Millennuim )
等同於
if( myDateTime.Year == 2000 ) // 取值
if( myDateTime.Year == ThisYear )
等同於
if( myDateTime.Year == ThisYear ) // 取變數
3. const 只能宣告為基本型別(e.g. int, float, decimal, double, string.),而 readonly 常數則無此限制。
// 編譯失敗
private const DateTime classCreation = new DateTime( 2000, 1, 1, 0, 0, 0 );
// 編譯成功
private static readonly DateTime classCreation = new DateTime( 2000, 1, 1, 0, 0, 0 );
4. 實務上 readonly 常數較 const 彈性。
假設我們在組件中定義了以下類別:
public class UsefulValues
{
public static readonly int StartValue = 5;
public const int EndValue = 10;
}
在客戶端參考該組件並執行以下程式:
for( int i = UsefulValues.StartValue; i < UsefulValues.EndValue; i++ )
Console.WriteLine( $"Value is {i}" );
輸出:
Value is 5
Value is 6
...
Value is 9
若日後某次更新了組件且不重新編譯客戶端程式:
public class UsefulValues
{
public static readonly int StartValue = 105;
public const int EndValue = 120;
}
預期輸出:
Value is 105
Value is 106
...
Value is 119
實際上會得到無輸出的結果。原因承 2. 所提及,由於 const 經過客戶端程式編譯後 IL Code 保存的是實際的值(10);組件的 const 即使改變,因客戶端程式並沒有重新編譯,EndValue 不會變化(仍然為 10)。
5. const 執行效能比 readonly 常數好。
為了驗證此論點,準備測試程式碼。
// 被參考的組件
public class ConstReadonlyTest
{
public const int ConstInt = 1;
public static readonly int ReadOnlyInt = 1;
}
// 客戶端測試程式碼
public class ConstReadonlyTestClient
{
public int Result { get; set; }
[Benchmark]
public void TestConst( )
{
Result = ConstReadonlyTest.ConstInt;
}
[Benchmark]
public void TestReadOnly( )
{
Result = ConstReadonlyTest.ReadOnlyInt;
}
}
BenchmarkDotNet 輸出:
// * Summary *
BenchmarkDotNet=v0.10.1, OS=Microsoft Windows NT 6.1.7601 Service Pack 1
Processor=Intel(R) Core(TM) i3-2100 CPU 3.10GHz, ProcessorCount=4
Frequency=3118212 Hz, Resolution=320.6966 ns, Timer=TSC
[Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1590.0
DefaultJob : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1590.0
Allocated=0 B
Method | Mean | StdErr | StdDev |
------------- |-------------- |-------------- |-------------- |
TestConst | 0.0144 ns | 0.0077 ns | 0.0298 ns |
TestReadOnly | 0.0344 ns | 0.0010 ns | 0.0037 ns |
// * Diagnostic Output - MemoryDiagnoser *
Note: the Gen 0/1/2 Measurements are per 1k Operations
// ***** BenchmarkRunner: End *****
結果看來 const 執行效率比 readonly 常數些微優異;但標準差很大,表示實際運行效率是浮動的。
1. 除非需要確保常數在編譯期間就為有效(e.g. attribute parameters, switch case labels, enum definitions.),抑或確定在每一次的 release 之間,定義的常數皆不需改變;否則使用 readonly 常數會比 const 更加彈性。
2. 當程式以執行效能為優先考量時,使用 const 會比 readonly 常數來的好。