[C#] const vs. static readonly

前段時間寫code的時候需要在類中定義一個常量的字符串,我就隨手寫了個const string = "xxx";。結(jié)果review別人的code時發(fā)現(xiàn)他們用的時static readonly,看起來效果差不多,那么究竟該用哪個呢?于是,我先把我們整個大工程里的code大概翻了翻,想看看大家都是怎么用的以及這兩種有沒有什么適用環(huán)境,結(jié)果是太混亂了,相同的情況下用這兩個的都有。所以,我決定梳理一下這兩個字段。

1. const與static readonly的最主要區(qū)別

我覺得conststatic readonly最大的區(qū)別在于,前者是靜態(tài)常量,后者是動態(tài)常量。意思就是const在編譯的時候就會對常量進(jìn)行解析,并將所有常量出現(xiàn)的地方替換成其對應(yīng)的初始化值。而動態(tài)常量static readonly的值則在運(yùn)行時才拿到,編譯的時候只是把它標(biāo)識為只讀,不會用它的值去替換,因此static readonly定義的常量的初始化可以比const稍微推遲一些。

為了更清楚得看到編譯時獲取值與運(yùn)行時獲取值的區(qū)別,這里有一個簡單的例子。
我們寫新建一個名為ConstStaticReadOnlyConsole Application Project和一個名為MyClassConfigPortable Class Library Project

// ConstStaticReadOnly Project
namespace ConstStaticReadOnly
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("const value is: {0}", MyClassConfig.ConstValue);
            Console.WriteLine("static readonly value is: {0}", MyClassConfig.ReadonlyValue);
        }
    }
}
// MyClassConfig Project
namespace ConstStaticReadOnly
{
    public class MyClassConfig
    {
        public const string ConstValue = "const";
        public static readonly string ReadonlyValue = "readonly";
    }
}

編譯運(yùn)行我們可以看到下面的輸出結(jié)果:

const value is: const
static readonly value is: readonly

下面我們修改一下兩個常量的值 (后面都加上value),然后只把MyClassConfig重新編譯一遍,并將生成的dll拷貝到ConstStaticReadOnlybuild目錄下替換原來的那個dll,再運(yùn)行ConstStaticReadOnly則可以得到下面的輸出:

const value is: const
static readonly value is: readonly value

從運(yùn)行結(jié)果可以看到,只有static readonly那個值變了,const還是原來的值。這是因?yàn)槲覀儧]有重新build ConstStaticReadOnly工程,而它里面用到的ConstValue值早在上次build的時候就已經(jīng)被替換成了"const"。那么,怎么才能把ConstStaticReadOnly里面的值變成最新的呢?很簡單,在ConstValue值修改以后,重新build ConstStaticReadOnly。

這樣一下子就可以看到在這里使用const的缺點(diǎn)了,如果我們的MyClassConfig被其他100個工程引用的話,每次修改MyClassConfig后一定要重新build這100個工程,不然的話這些工程里的const值就不會更新。

當(dāng)然上面的例子并不是說const不好或者我們不要用const,只是說有些情況不適合用const,而且const也有自身的優(yōu)點(diǎn),如編譯時就被解析從而免去了運(yùn)行時的一些調(diào)用,既可以聲明在類中也可以聲明在函數(shù)體內(nèi)等。
下面我們就來分析一下兩者分別適用的情況。

2. 什么時候用const

(1)對于我們非常確定不會改變的常量,(這里的改變不是指運(yùn)行時試圖重新賦值來改變,而是指code中寫的那個值被修改)例如:
const int CM_IN_A_METER = 100;

(2)在函數(shù)體內(nèi)聲明的常量,例如:

void func()
{
    const double PI = 3.14;
    // use PI to do some calculation
}

(3)用于attribute里,例如

public static class Text 
    {
        public const string ConstDescription = "This can be used.";
        public readonly static string ReadonlyDescription = "Cannot be used.";
    }

    public class Fun 
    {
        // You should add using System.ServiceModel.Description (System.ServiceModel.dll);
        // and using System.ComponentModel (System.dll);
        [Description(Text.ConstDescription)]
        public int BarThatBuilds { get; set; }

        [Description(Text.ReadOnlyDescription)]
        public int BarThatDoesNotBuild { get; set; }
    }

attribute里面只能使用const常量,使用static readonly會出現(xiàn)編譯錯誤。

Error   1   'ConstStaticReadOnly.Text' does not contain a definition for 'ReadOnlyDescription'

(4)當(dāng)你需要implicit conversion時
下面是stackoverflow上有人提供的一個例子,采用conststatic readonly得到的結(jié)果會不一樣。

const int y = 42;
static void Main()
{
    short x = 42; 
    Console.WriteLine(x.Equals(y)); // True
}

static readonly int y = 42;
static void Main()
{ 
    short x = 42; 
    Console.WriteLine(x.Equals(y)); // False
}

The reason is that the method x.Equals has two overloads, one that takes in a short (System.Int16) and one that takes an object (System.Object). Now the question is whether one or both apply with my y argument.

對于const修飾的int常量情況,存在implicit conversion from int to short,這樣比較的時候就使用了short版本的Equals;而static readonly修飾的int則不具有隱士轉(zhuǎn)換的功能,比較的時候使用的objectEquals,如果你認(rèn)為這種情況下他們應(yīng)該相等,則可以在比較的時候進(jìn)行顯示轉(zhuǎn)換,如x.Equals((short)y)

3. 什么時候用static readonly

(1)需要根據(jù)config文件里的值來初始化的
為了方便管理常量,我們通常會把一個project或者solution里的所有常量集中起來,采用config文件進(jìn)行配置。這樣不僅便于管理、修改和維護(hù),而且可以在不同的環(huán)境下使用不同的config文件來初始化code里的那些常量。const修飾的常量必須在聲明的時候就初始化在code里,肯定是做不到這一點(diǎn)的,所以可以采用static readonly來聲明這些常量,然后在構(gòu)造函數(shù)里load config文件,對所有相應(yīng)的常量進(jìn)行初始化。

(2)可能會發(fā)生變化的常量
其實(shí)(1)也可以看做是這一類,只是我覺得(1)比較常用,而且像(1)那樣對常量進(jìn)行集中管理是一種很好的習(xí)慣,所以才單獨(dú)提出來了。下面來對可能發(fā)生變化的常量舉一個例子,

class MyMathLib
{
    private static readonly PI = 3.14;
}

為什么說PI是一個可能會變得常量呢?因?yàn)椴煌闆r下你的工程對精度的要求可能不一樣,某天如果突然間發(fā)現(xiàn)只保留兩位小數(shù)時精度不夠時,可能就會把它改成3.14159了。另外,這里的PI跟上面函數(shù)體內(nèi)需要用到的PI必須用const并不矛盾,雖然函數(shù)體內(nèi)的PI也可能會改變,但是并不要緊,因?yàn)樗呀?jīng)在函數(shù)體內(nèi)了,改變后肯定會同時編譯PI常量和那個函數(shù)。

(3)需要new操作符初始化的
const一般用于修飾值類型或者string(注意string是引用類型)。因?yàn)橐妙愋停ǔ?code>string)是要通過new關(guān)鍵字來初始化的,而const聲明的常量是不能用new來初始化的,所以如果你一定要用const來修飾一個引用類型(string除外)的常量,請初始化為null。例如,Fun f = new Fun();會引起下面的編譯錯誤:

Error   1    A const field of a reference type other than string can only be initialized with null.

所以,如果你要將引用類型的非空值定義為常量,你需要使用static readonly,

private static readonly List<int> test = new List<int> {1, 2, 3};

(4)關(guān)于private與public
類中static readonly修飾的常量應(yīng)該用private還是public呢?如果用private,那客戶端那邊就不能直接訪問了,所以就定義成public?對于一般的值類型或者string,定義成public static readonly當(dāng)然沒問題,這也是我們常用的。

可是對下面一種情況可能會有問題:

// ConstStaticReadOnly Project
namespace ConstStaticReadOnly
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("x={0}, y={1}", MyClassConfig.ReadonlyPoint.x, MyClassConfig.ReadonlyPoint.y);
             // MyClassConfig.ReadonlyPoint = new Point() is not allowed
            // We cannot change the reference of ReadonlyPoint
            // But we can change the fields in ReadonlyPoint
            MyClassConfig.ReadonlyPoint.x = 3;
            MyClassConfig.ReadonlyPoint.y = 4;
            Console.WriteLine("x={0}, y={1}", MyClassConfig.ReadonlyPoint.x, MyClassConfig.ReadonlyPoint.y);
        }
    }
}
// MyClassConfig Project
namespace ConstStaticReadOnly
{
    public class Point
    {
        public int x;
        public int y;
        public Point(int a, int b)
        {
            x = a;
            y = b;
        }
    }

    public class MyClassConfig
    {
        public static readonly Point ReadonlyPoint = new Point(1, 2);
    }
}

輸出結(jié)果:

x=1, y=2
x=3, y=4

我們的本意應(yīng)該是讓ReadonlyPoint不能被外界改變,現(xiàn)在看來上面的static readonly并沒有達(dá)到這個效果。這是因?yàn)?code>static readonly修飾的常量只能保證reference不能變,也就是不能對ReadonlyPoint進(jìn)行重新賦值,但是ReadonlyPoint引用的那個Point里面的值是可以被改變的,這叫mutable reference types。

所以在用FxCop 對代碼進(jìn)行分析時,會出現(xiàn)Do not declare read only mutable reference types的warning。也就是說上面那樣用public static readonly修飾的ReadonlyPoint并不是安全的,下面有一種解決方案:
ReadonlyPoint聲明為private或者protected,然后提供一個僅提供get函數(shù)的property來返回內(nèi)部的ReadonlyPoint

protected static readonly Point readonlyPoint = new Point(1, 2);

public static Point ReadonlyPoint
{
    get
    {
        return readonlyPoint;
    }
}

4. 小結(jié)

(1)const常量在編譯時解析;而static readonly常量在運(yùn)行時解析。
(2)const常量必須在定義時初始化;而static readonly常量可以在定義時初始化,也可以在構(gòu)造函數(shù)中初始化;
(3)非常確定不會改變的常量值可以用const,必須寫在函數(shù)體內(nèi)的常量需要用const,需要被attributes用到的常量應(yīng)該用const。
(4)常量需要被客戶端引用,且可能會改變,應(yīng)該用static readonly。

參考文獻(xiàn):

  1. const (C# Reference)
  2. readonly (C# Reference)
  3. What is the difference between const and readonly?
  4. Static readonly vs const
  5. const和readonly區(qū)別
  6. Do not declare read only mutable reference types
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 注:這是第三遍讀《C語言深度解剖》,想想好像自從大學(xué)開始就沒讀完過幾本書,其中譚浩強(qiáng)的那本《C語言程序設(shè)計(jì)(第四版...
    HavenXie閱讀 1,923評論 1 6
  • const 引用 const 引用是指向 const 對象的引用:const int ival = 1024;co...
    rogerwu1228閱讀 744評論 0 1
  • (1)可以定義 const 常量 (2)const 可以修飾函數(shù)的參數(shù)、返回值. 詳細(xì)內(nèi)容: 1、什么是const...
    幽鬼09閱讀 745評論 0 4
  • 雨過天晴云浪漫,薄霧隨風(fēng)卷。田野又增輝,黃土高坡、收麥驕陽贊。 一年一度糧倉滿,種地多思變。大路去家鄉(xiāng),渭水長流、...
    木貞ma閱讀 224評論 2 2
  • 親愛的晉善: 新年快樂,今天是大年初一,農(nóng)歷新年的第一天,每一個月的第一天都是初一,中國人是相當(dāng)重視這樣的第一天,...
    丁爸閱讀 682評論 0 51

友情鏈接更多精彩內(nèi)容