《Effective C#》Item 7:推薦使用不可改變的原子值類型

首先來解釋一下標題,原標題爲《Prefer Immutable Atomic Value Type》,因此對於標題的理解要分成三部分,第一部分爲不可改變,第二部分爲原子,最後一個部分爲值類型。最後一部分,我不多說了,限制此章適用的範圍。對於什麼是不可改變類型,這裏的意思是指此類型的變量一旦產生其成員就不能發生變化。至於原子類型,我以前在CSDN也經常提到,例如保證操作的原子性之類的語句,那麼一個原子類型,其的子成員爲不可分割的一部分,不能單獨被操作。

 

聽了標題解釋,難免有些人會問,爲什麼要加上這樣的限制,或者說這樣做的好處是什麼。爲了解開這個疑團,我用一個例子來說明,去定義一個電話號碼的值類型,一般形式如下:

    public struct Phone

    {

        private string strCountry_Code;

        private string strCity_Code;

        private string strPhone_Number;

        public string Country_Code

        {

            get{ return strCountry_Code; }

            set{ strCountry_Code = value;}

        }

        public string City_Code

        {

            get{ return strCity_Code; }

            set{ strCity_Code = value;}

        }

        public string Phone_Number

        {

            get{ return strPhone_Number; }

            set{ strPhone_Number = value;}

        }

 

        // Constructor

        public Phone( string sCountry_Code, string sCity_Code, string sPhone_Number )

        {

            strCountry_Code = sCountry_Code;

            strCity_Code = sCity_Code;

            strPhone_Number = sPhone_Number;

        }

    }

 

這樣去初始化一個Phone類型變量的話,同時也可以做類似如下的相關操作。

    Phone myPhone = new Phone( "086", "010", "66666666" );

    myPhone.City_Code = "021";

    myPhone.Phone_Number = "777777777";

 

大多數人覺得如上的代碼沒有什麼問題,不過稍微明眼的人看了如上的代碼,就會立刻覺得有潛在的危險。作爲一個Phone類型變量來說,國家區號,城市區號,以及電話號碼來說是一個整體。因此動態修改其中的某一個值,會造成其他兩個無效。也就是說如上的代碼直接修改City_Code的時候,對於myPhone來說其他兩個變量Country_Code以及Phone_Number來說,此時是無效的。不過這種錯誤在單線程中不是很明顯,但是在多線程中是致命的,而且很難查出來。有人可能會說了,在修改的時候加上Lock語句或者互斥標識來避免。這樣是可以避免,但是試問一下,類型是你創建的,你怎麼要求別人在使用你這個類型的時候做過多的操縱呢,爲什麼你不在創建此類型的時候就直接把這條路堵死。如果明白了這一點,就理解了這篇文章推薦的目的,即與其後期增加代碼彌補,不如在前期就編寫正確的代碼。

 

知道這樣做的原因,接下來就是如何去實現。不過在實現之前,要區分什麼樣的數據類型可以定義成不可變的原子類型。也就是說,你如何區分一個類型是一個整體,而且每個分支不能獨立於整體而存在。例如對於聯繫方式這個類型來說,它包括電話號碼、住址等等。它可以看爲一個整體,但是分支可以脫離這個整體而存在,因此它不是一個原子類型。對於如何具體區分,很難有一個統一的方法,畢竟適應的環境不同,操作以及實現也不同。不過對於原子類型,有一個唯一判斷方式,就是每個分支能否獨立於整體而被操作,這個的是與否決定是否爲原子類型。

 

那麼如何去定義一個不可變的原子值類型呢,大致要對原有的類型做兩個處理,一個就是把所有成員加上readonly標示,即只能在構造函數中被修改;另一個就是刪除屬性set部分。對於Phone這個類型來說,經過處理後,正確的形式如下:

    public struct Phone

    {

        private readonly string strCountry_Code;

        private readonly string strCity_Code;

        private readonly string strPhone_Number;

        public string Country_Code

        {

            get{ return strCountry_Code; }

        }

        public string City_Code

        {

            get{ return strCity_Code; }

        }

        public string Phone_Number

        {

            get{ return strPhone_Number; }

        }

 

        // Constructor

        public Phone( string sCountry_Code, string sCity_Code, string sPhone_Number )

        {

            strCountry_Code = sCountry_Code;

            strCity_Code = sCity_Code;

            strPhone_Number = sPhone_Number;

        }

    }

 

這樣對於一個Phone類型變量,只能通過new來創建(也就是說在輸入三個有效的數據後,一個Phone類型變量才能產生)。

 

在此有人會問,除了new是否還有其他方法來進行修改。這是沒有任何問題的,首先你只要理解了原子類型的意義,保證分支不會單獨被修改即可,因此可以實現類似於“Phone.Parse”或者“Phone.From”之類的函數來形成一個新的Phone變量。

 

在實現不可變的原子值類型的時候,要防止類型中包括引用類型分支的時候,在進行成員賦值的時候,防止淺copy,這方面我就不多說了,參看我以前寫的文章就可以明白(這裏的目的也就是一點,防止局部破壞原子類型的分支)。

http://blog.csdn.net/Knight94/archive/2006/07/01/861383.aspx

http://blog.csdn.net/Knight94/archive/2006/06/04/772886.aspx

 
發佈了87 篇原創文章 · 獲贊 9 · 訪問量 100萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章