C#中const和readonly的區別

來源:互聯網
上載者:User

C#引入了readonly修飾符來表示唯讀域,const來表示不變常量。顧名思義對唯讀域不能進行寫操作,不變常量不能被修改,這兩者到底有什麼區別呢?唯讀域只能在初始化--聲明初始化或構造器初始化--的過程中賦值,其他地方不能進行對唯讀域的賦值操作,否則編譯器會報錯。唯讀域可以是執行個體域也可以是靜態域。唯讀域的類型可以是C#語言的任何類型。但const修飾的常量必須在聲明的同時賦值,而且要求編譯器能夠在編譯時間期計算出這個確定的值。const修飾的常量為靜態變數,不能夠為對象所擷取。const修飾的值的類型也有限制,它只能為下列類型之一(或能夠轉換為下列類型的):sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, enum類型, 或參考型別。值得注意的是這裡的參考型別,由於除去string類型外,所有的類型出去null值以外在編譯時間期都不能由編譯器計算出他們的確切的值,所以我們能夠聲明為const的參考型別只能為string或值為null的其他參考型別。顯然當我們聲明一個null的常量時,我們已經失去了聲明的意義 --這也可以說是C#設計的尷尬之處!

這就是說,當我們需要一個const的常量時,但它的類型又限制了它不能在編譯時間期被計算出確定的值來,我們可採取將之聲明為static readonly來解決。但兩者之間還是有一點細微的差別的。看下面的兩個不同的檔案:

//file1.cs
//csc /t:library file1.cs
using System;
namespace MyNamespace1
{
public class MyClass1
{
                public static readonly int myField = 10;
        }
}

//file2.cs
//csc /r:file1.dll file2.cs
using System;
namespace MyNamespace2
{
public class MyClass1
{
                public static void Main()
                {
                        Console.WriteLine(MyNamespace1.MyClass1.myField);
                }
        }
}

我們的兩個類分屬於兩個檔案file1.cs 和file2.cs,並分開編譯。在檔案file1.cs內的域myField聲明為static readonly時,如果我們由於某種需要改變了myField的值為20,我們只需重新編譯檔案file1.cs為file1.dll,在執行 file2.exe時我們會得到20。但如果我們將static readonly改變為const後,再改變myField的初始化值時,我們必須重新編譯所有引用到file1.dll的檔案,否則我們引用的 MyNamespace1.MyClass1.myField將不會如我們所願而改變。這在大的系統開發過程中尤其需要注意。實際上,如果我們能夠理解 const修飾的常量是在編譯時間便被計算出確定的值,並代換到引用該常量的每一個地方,而readonly時在運行時才確定的量--只是在初始化後我們不希望它的值再改變,我們便能理解C#設計者們的良苦用心,我們才能徹底把握const和readonly的行為!

---------------------

Features:

readonly和const都是用來標識常量的[1]。
const可用於修飾class的field或者一個局部變數(local variable);而readonly僅僅用於修飾class的field。
const常量的值必定在編譯時間就已明確並且恒定的;而readonly常量卻有一點不同,那就是其值可以在運行時編譯,當然,它也必須遵守作為常量的約束,那就是值必須恒定不變。
const常量必須在聲明的同時對其進行賦值,並且確保該值在編譯時間可確定並恒定;而readonly常量則可以根據情況選擇在聲明的同時對其賦予一個編譯時間確定並恒定的值,或者將其值的初始化工作交給執行個體建構函式(instant constructor)完成。如:public readonly string m_Now = DateTime.Now.ToString();,m_Now會隨著運行時實際情況變化而變化。
const常量屬於類層級(class level)而不是執行個體對象層級(instant object level),並且它不能跟static結合一起使用,該常量的值將由整個類的所有執行個體對象共同分享(詳細論述參見後面的Remark地區)。
readonly常量既可以是類層級也可以是執行個體對象層級的,這取決於它的聲明以及初始化工作怎麼實施。readonly可以與static結合使用,用於指定該常量屬於類層級,並且把初始化工作交由靜態建構函式(static constructor)完成(有關如何把readonly常量聲明為類層級或執行個體對象層級的論述清參見後面的Remark地區) 。
能被const修飾聲明為常量的類型必須是以下的基元類型(primitive type):sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, float, bool, decimal, string。
object, 數組(Array)和結構(struct)不能被聲明為const常量。
一般情況下,參考型別是不能被聲明為const常量的,不過有一個例外:string。該參考型別const常量的值可以有兩種情況,string或 null。其實,string雖然是參考型別,但是.NET卻對它特別處理,這種處理叫做字串恒定性(immutable),使得string的值具有唯讀特性。有關字串恒定性的內容,可以參考《Microsoft .NET架構程式設計(修訂版)》。

Examples:

using System;

public class Order
{
    public Order()
    {
        Guid guid = Guid.NewGuid();
        ID = guid.ToString("D");
    }

    // 對於每一份訂單,其訂單序號都是即時確定的常量。
    public readonly string ID;

    public override string ToString()
    {
        return "Order ID: " + ID;
    }
}
Explaintion:

如果結合資料庫使用,ID field通常都會都會與某個表的主健(primary key)關聯起來,如Orders表的OrderID。
資料庫的主健通常採用以下三種方式:
自動遞增值。你可以通過把DataColumn.AutoIncrement設定為true值來啟用自動遞增特性。
唯一名稱。這個是使用自己定義的演算法來產生一個唯一序號。
GUID(通用唯一識別碼)。你可以通過System.Guid結構來產生GUID,如上例。
using System;

class Customer
{
    public Customer(string name, int kind)
    {
        m_Name = name;
        m_Kind = kind;
    }

    public const int NORMAL = 0;
    public const int VIP = 1;
    public const int SUPER_VIP = 2;

    private string m_Name;
    public string Name
    {
        get { return m_Name; }
    }

    private readonly int m_Kind;
    public int Kind
    {
        get { return m_Kind; }
    }

    public override string ToString()
    {
        if(m_Kind == SUPER_VIP)
            return "Name: " + m_Name + "[SuperVip]";
        else if(m_Kind == VIP)
            return "Name: " + m_Name + "[Vip]";
        else
            return "Name: " + m_Name + "[Normal]";
    }
}

Remarks:

一般情況下,如果你需要聲明的常量是普遍公認的並作為單個使用,例如圓周率,黃金分割比例等。你可以考慮使用const常量,如:public const double PI = 3.1415926;。如果你需要聲明常量,不過這個常量會隨著實際的運行情況而決定,那麼,readonly常量將會是一個不錯的選擇,例如上面第一個例子的訂單號Order.ID。
另外,如果要表示對象內部的預設值的話,而這類值通常是常量性質的,那麼也可以考慮const。更多時候我們對原始碼進行重構時(使用Replace Magic Number with Symbolic Constant),要去除魔數(Magic Number)的影響都會藉助於const的這種特性。
對於readonly和const所修飾的變數究竟是屬於類層級的還是執行個體對象層級的問題,我們先看看如下代碼:

使用Visual C#在Main()裡面使用IntelliSence插入Constant的相關field的時候,發現ReadonlyInt和 InstantReadonlyInt需要指定Constant的執行個體對象;而ConstInt和StaticReadonlyInt卻要指定 Constant class(參見上面代碼)。可見,用const或者static readonly修飾的常量是屬於類層級的;而readonly修飾的,無論是直接通過賦值來初始化或者在執行個體建構函式裡初始化,都屬於執行個體對象層級。
一般情況下,如果你需要表達一組相關的編譯時間確定常量,你可以考慮使用枚舉類型(enum),而不是把多個const常量直接嵌入到class中作為 field,不過這兩種方式沒有絕對的孰優孰劣之分。

using System;

enum CustomerKind
{
    SuperVip,
    Vip,
    Normal
}

class Customer
{
    public Customer(string name, CustomerKind kind)
    {
        m_Name = name;
        m_Kind = kind;
    }

    private string m_Name;
    public string Name
    {
        get { return m_Name; }
    }

    private CustomerKind m_Kind;
    public CustomerKind Kind
    {
        get { return m_Kind; }
    }

    public override string ToString()
    {
        return "Name: " + m_Name + "[" + m_Kind.ToString() + "]";
    }
}

然而,當這種結合使用枚舉和條件判斷的代碼阻礙了你進行更靈活的擴充,並有可能導致日後的維護成本增加,你可以代之以多態,使用Replace Conditional with Polymorphism來對代碼進行重構。(有關多態的詳細介紹,請參見《今天你多態了嗎?》一文。)

Comments:

readonly field準確來說應該翻譯成為“唯讀域”,這裡是為了統一翻譯用語才將它和const兩者所修飾的量都說成“常量”,希望沒有引起誤會。
------------------------------------------------

C# FAQ: const和static readonly有什麼區別?
我們都知道,const和static readonly的確很像:通過類名而不是對象名進行訪問,在程式中唯讀等等。在多數情況下可以混用。
二者本質的區別在於,const的值是在編譯期間確定的,因此只能在聲明時通過常量運算式指定其值。而static readonly是在運行時計算出其值的,所以還可以通過靜態建構函式來賦值。
明白了這個本質區別,我們就不難看出下面的語句中static readonly和const能否互換了:
1. static readonly MyClass myins = new MyClass();
2. static readonly MyClass myins = null;
3. static readonly A = B * 20;
   static readonly B = 10;
4. static readonly int [] constIntArray = new int[] {1, 2, 3};
5. void SomeFunction()
   {
      const int a = 10;
      ...
   }

1:不可以換成const。new操作符是需要執行建構函式的,所以無法在編譯期間確定
2:可以換成const。我們也看到,Reference類型的常量(除了String)只能是Null。
3:可以換成const。我們可以在編譯期間很明確的說,A等於200。
4:不可以換成const。道理和1是一樣的,雖然看起來1,2,3的數組的確就是一個常量。
5:不可以換成readonly,readonly只能用來修飾類的field,不能修飾局部變數,也不能修飾property等其他類成員。

因此,對於那些本質上應該是常量,但是卻無法使用const來聲明的地方,可以使用static readonly。例如C#規範中給出的例子:

public class Color
{
    public static readonly Color Black = new Color(0, 0, 0);
    public static readonly Color White = new Color(255, 255, 255);
    public static readonly Color Red = new Color(255, 0, 0);
    public static readonly Color Green = new Color(0, 255, 0);
    public static readonly Color Blue = new Color(0, 0, 255);static readonly需要注意的一個問題是,對於一個static readonly的Reference類型,只是被限定不能進行賦值(寫)操作而已。而對其成員的讀寫仍然是不受限制的。

public static readonly MyClass myins = new MyClass();

myins.SomeProperty = 10;  //正常
myins = new MyClass();    //出錯,該對象是唯讀

但是,如果上例中的MyClass不是一個class而是一個struct,那麼後面的兩個語句就都會出錯。

    private byte red, green, blue;

    public Color(byte r, byte g, byte b)
    {
        red = r;
        green = g;
        blue = b;
    }
}

using System;

namespace ConstantLab
{
    class Program
    {
        static void Main(string[] args)
        {
            Constant c = new Constant(3);
            Console.WriteLine("ConstInt = " + Constant.ConstInt.ToString());
            Console.WriteLine("ReadonlyInt = " + c.ReadonlyInt.ToString());
            Console.WriteLine("InstantReadonlyInt = " + c.InstantReadonlyInt.ToString());
            Console.WriteLine("StaticReadonlyInt = " + Constant.StaticReadonlyInt.ToString());

            Console.WriteLine("Press any key to continue");
            Console.ReadLine();
        }
    }

    class Constant
    {
        public Constant(int instantReadonlyInt)
        {
            InstantReadonlyInt = instantReadonlyInt;
        }

        public const int ConstInt = 0;

        public readonly int ReadonlyInt = 1;

        public readonly int InstantReadonlyInt;

        public static readonly int StaticReadonlyInt = 4;
    }
}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.