5.2 將值型別盡可能實現為具有常量性和原子性的型別
具有常量性的型別很簡單:
· 如果構造的時候驗證了引數的有效性,之後就一直有效;
· 省去了許多錯誤檢查,因為禁止更改;
· 確保執行緒安全,因為多個reader
訪問到同樣的內容;
· 可以安全地暴露給外界,因為呼叫者不能更改物件的內部狀態。
具有原子性的型別都是單一的實體,我們通常會直接替換乙個原子型別的整個內容。
下面是乙個典型的可變型別:
public
struct
address
set}
public
string
province
set}
public
intzipcode
set}}
下面建立乙個例項:
address address=
newaddress();
address.city=
"chengdu";
address.province=
"sichuan";
address.zipcode=
610000;
然後更改這個例項:
address.city=
"nanjing";
//now province and zipcode are invalid
address.zipcode=
210000;
//now province is still invalid
address.province=
"jiangsu";
可見,內部狀態的改變意味著可能違反物件的不變式(invariant
),至少是臨時的違反。如果上面是乙個多執行緒的程式,那麼在
city
更改的過程中,另乙個執行緒可能看到不一致的資料檢視。如果不是多執行緒的程式,也有問題:
· 當zipcode
的值無效而丟擲異常時,物件僅作了一部分改變,因此處於無效的狀態,為了修復這個問題,需要在
address
中新增相當多的內部校驗**;
· 為了實現異常安全,我們需要在所有改變多個欄位的客戶**處放上防禦性的**;
· 執行緒安全也要求我們在每乙個屬性的訪問器上新增執行緒同步檢查。
顯然,這是乙個相當可觀的工作量。下面我們把address
實現為常量型別:
public
struct
address
public
string
city
}public
string
province
}public
intzipcode}}
如果要改變address
,不能修改現有的例項,只能建立乙個新的例項:
address address=
newaddress("chengdu","sichuan",610000);
//create a instance
address=
newaddress("nanjing","jiangsu", 210000);
//modify the instance
address將不存在任何無效的臨時狀態。那些臨時狀態只存在於
address
的建構函式執行過程中。這樣一來,
address
是異常安全的,也是執行緒安全的。
5.3 確保
0為值型別的有效狀態
.net的預設初始化機制會將引用型別設定為二進位制意義上的0,即
null
。而對於值型別,不論我們是否提供建構函式,都會有乙個預設的建構函式,將其設定為0。
一種典型的情況是列舉:
public
enum
***
然後用做值型別的成員:
public
struct
employee
建立employee
結構體將得到乙個無效的
***字段:
employee employee=
newemployee ();
employee的
_***
是無效的,因為其為
0。我們應該將
0作為乙個為初始化的值明確表示出來:
public
***
如果值型別中包含引用型別,會出現另一種初始化問題:
public
struct
errorlog
然後建立乙個errorlog
:errorlog errorlog=
newerrorlog ();
errorlog的
_message
欄位將是乙個空引用。我們應該通過乙個屬性來將
_message
暴露給客戶**,從而使該問題限定在
errorlog
的內部:
public
struct
errorlog
set}
//other}
5.4 儘量減少裝箱和拆箱
裝箱指把乙個值型別放入乙個未具名型別的引用型別中,比如:
intvaluetype=
0;object
referencetype=
i;//boxing
拆箱則是從前面的裝箱物件中取出值型別:
object
referencetype;
intvaluetype=
(int
)referencetype;
//unboxing
裝箱和拆箱是比較耗費效能的,還會引入一些詭異的bug
,我們應當避免裝箱和拆箱。
裝箱和拆箱最大的問題是會自動發生。比如:
console.writeline("a few numbers: , .",25,32);
其中,console.writeline()
接收的引數型別是
(string
,object
,object)
。因此,實際上會執行以下操作:
inti=
25;obeject o=
i;//boxing
然後把o
傳給writeline()
方法。在
writeline()
方法的內部,為了呼叫i上的
tostring()
方法,又會執行:
inti=
(int
)o;//unboxing
string
output=
i,tostring();
所以正確的做法應該是:
console.writeline("a few numbers: , .",25.tostring(),32.tostring());
25.tostring()只是執行乙個方法並返回乙個引用型別,不存在裝箱
/拆箱的問題。
另乙個典型的例子是arrylist
的使用:
public
struct
employee
public
string
name
set}
public
override
string
tostring()
}arraylist employees=
newarraylist();
employees.add(
newemployee("old name"));
//boxing
employee ceo=
(employee)employees[0];
//unboxing
ceo.name=
"new name";
//employees[0].tostring() is still "old name"
上面的**不僅存在效能的問題,還容易導致錯誤發生。
在這種情況下,更好的做法是使用泛型集合:
listemployees
=new
list();
由於list
是強型別的集合,
employees.add()
方法不進行型別轉換,所以不存在裝箱
/拆箱的問題。
6. 總結
陣列的元素,不管是引用型別還是值型別,都儲存在託管堆上。
引用型別在棧中儲存乙個引用,其實際的儲存位置位於託管堆。為了方便,本文簡稱引用型別部署在託管推上。
值型別總是分配在它宣告的地方:作為欄位時,跟隨其所屬的變數(例項)儲存;作為區域性變數時,儲存在棧上。
應該盡可能地減少裝箱和拆箱。
C 值型別與引用型別
1.主要內容 型別的基本概念 值型別深入 引用型別深入 值型別與引用型別的比較及應用 2.基本概念 c 中,變數是值還是引用僅取決於其資料型別。c 的基本資料型別都以平台無關的方式來定義,c 的預定義型別並沒有內置於語言中,而是內置於.net framework中。net使用通用型別系統 cts 定...
c 引用型別與值型別
c 的值型別包括 結構體 數值型別,bool型,使用者定義的結構體 列舉,可空型別。c 的引用型別包括 陣列,使用者定義的類 介面 委託,object,字串。在c 中函式傳值的特點 1.對於複雜的資料型別,按引用傳遞的效率更高,因為在按值傳遞時,必須複製大量的資料 2.除非特別指定,所有的引用型別都...
C 值型別與引用型別
資料型別分為 值型別 引用型別 值型別 int char double bool 結構 struct 列舉 enum 儲存在堆疊中 引用型別 類 string。陣列 介面 儲存在託管堆中 2.值型別 變數儲存物件的值,賦值會建立值的副本,修改任何乙個副本,不會影響其他的 副本 int x 5 int...