編譯器直接支援的資料型別稱為基元型別(primitive type)。基元型別直接對映到framework類庫(fcl)中存在的型別。
fcl型別在c#中對應的基元型別:
c#基元型別
fcl型別
是否符合cls
說明
sbyte
system.sbyte
否 有符號8位值
byte
system.byte
是 無符號8位值
short
system.int16
是 有符號16位值
ushort
system.uint16
否 無符號16位值
int
system.int32
是 有符號32位值
uint
system.uint32
否 無符號32位值
long
system.int64
是 有符號64位值
ulong
system.uint64
否 無符號64位值
char
system.char
是 16位unicode字元
float
system.single
是 ieee32位浮點值
double
system.double
是 ieee64位浮點值
bool
system.boolean
是 true/false值
decimal
system.decimal
是 128位高精度浮點值,常用於不容許捨入誤差的金融計算
string
system.string
是 字元陣列
object
system.object
是 所有型別的基型別
dynamic
system.object
是 對於clr,dynamic和object完全一致。但c#編譯器允許使用簡單的語法讓dynamic變數參與動態排程
從某個角度來說,可以認為c#編譯器自動為所有源**檔案新增using指令為型別建立別名。
using int = system.int32;
說到這裡應該就可以理解string和string的關係了,c#的string(關鍵字)直接對映到system.string(型別),所以兩者實際上沒有區別。
對基元型別執行算術運算時可能造成溢位。不同語言處理溢位的方式不同,c/c++不將溢位視為錯誤,允許值回滾;microsoft visual basic則將溢位視為錯誤,並在檢測到溢位時丟擲異常。而c#允許程式設計師自己決定如何處理溢位,溢位檢查預設關閉。下面介紹兩種方式開啟或關閉溢位檢查:
特定區域控制溢位檢查
c#通過checked和unchecked操作符在**的特定區域進行溢位檢查。
byte b = 100;
b = checked((byte)(b + 200)); //丟擲overflowexception異常
c#也支援checked和unchecked語句塊,對語句塊中的所有語句都進行或不進行溢位檢查
checked
全域性性控制溢位檢查
只需開啟編譯器的/checked+開關,系統就會對沒有顯示標記checked或unchecked的**進行溢位檢查(當然這種情況下應用程式執行起來會慢一些)。要在visual studio中更改checked設定,開啟專案屬性,按照以下步驟即可找到開關:properties|build|advanced|check for arithmetic overflow/underflow
##引用型別和值型別
記憶體必須從託管堆分配。
堆上分配的每個物件都有一些額外成員(型別物件及同步快索引),這些成員必須初始化。
從託管堆分配物件時,可能強制執行一次垃圾**。
當然,也不是所有的情況下值型別都可以提高程式效能,甚至在某些特定的情況下會對效能造成損害。例如當實參以值型別方式傳遞或方法返回乙個值型別時,例項中的字段會複製到呼叫者分配的記憶體中,若例項較大,便會對應用程式的效能造成影響。
許多時候,都需要獲取對值型別例項的引用。首先,我們來看一段**:
arraylist arr = new arraylist();
for (int i = 0; i < 10; i++)
這段**很簡單,迴圈10次向arraylist中新增int型別變數i。現在我們考慮一下,arraylist中儲存的究竟是什麼?要搞清楚這個問題,先看一下add方法的源**:
public virtual int add(object value)
注意這裡的引數型別是object型別,也就是說add方法獲取對託管堆上的乙個物件的引用來作為引數。
所以,為了使**正常工作,int值型別必須轉換成託管堆中的物件,而且要獲得該物件的引用。將值型別轉換為引用型別要使用裝箱操作。以下,是對值型別的例項進行裝箱操作的過程:
在託管堆上分配記憶體。包括值型別各字段所需的記憶體量,還要加上型別物件指標和同步塊索引的記憶體量。
將值型別的字段複製到新分配的堆記憶體。
返回物件位址。
這時我們再回到開頭的**,c#編譯器檢測到向要求引用型別的方法傳遞值型別,所以自動生成**對物件進行裝箱操作。int值型別例項i的字段複製到新分配的int物件中,並將位址返回給add方法。
int.tostring()方法不進行裝箱操作
以下是官方文件中對裝箱的描述,int.tostring()方法顯然不滿足裝箱的條件。
裝箱用於在垃圾**堆中儲存值型別。 裝箱是值型別到 object 型別或到此值型別所實現的任何介面型別的隱式轉換。 對值型別裝箱會在堆中分配乙個物件例項,並將該值複製到新的物件中。 ——裝箱和取消裝箱了解了裝箱操作後,接著談談拆箱,假設要獲取arraylist中的第乙個元素。
int x = (int)arr[0];
獲取已裝箱的int物件的位址。這個過程稱為拆箱。
將字段包含的值複製到執行緒棧的值型別例項中。
注意:對物件進行拆箱時,只能轉型為最初未裝箱的值型別。
int32 x = 5;
object o = x;
//int16 y = (int16)o;
int16 y = (int16)(int32)o; //ok
##dynamic基元型別
在大多數情況下,dynamic型別與object型別的行為類似。如果操作包括dynamic型別的表示式,那麼不會通過編譯器對該操作進行解析或型別檢查。編譯器將有關操作資訊打包在一起,之後這些資訊會用於在執行**估操作。在此過程中,dynamic型別的變數會編譯為object型別的變數。因此,dynamic型別只在編譯時存在,在執行時則不存在。
請看以下示例**:
dynamic d;
int x = 5;
d = x + x;
console.writeline(d); //10
string s = "5";
d = s + s;
console.writeline(d); //"55"
注意,所有的表示式都能隱式轉型為dynamic,因為所有表示式最終都生成從object派生的型別。正常情況下,編譯器不允許將表示式從object型別隱式轉換為其他型別,但編譯器允許使用隱式轉型將表示式從dynamic轉型為其他型別。
object o = 10;
//int i = o; //error:cannot implicitly convert type 'object' to 'int'.
int i = (int)o; //ok
dynamic d = 10;
int j = d; //ok
CLR 基元型別 引用型別和值型別
前言 今天重新看了下關於clr基元型別的東西,覺得還是有必要將其記錄下來,畢竟這是理解clr成功 之路上的重要一步,希望你也和我一樣。基元型別 編譯器直接支援的資料型別稱之為基元型別,針對那些程式設計師自定義的型別而言。所有基元型別 直接對映到fcl framework class library ...
第五章 基元型別引用型別和值型別
checked開啟時,如果發生溢位會丟擲異常,unchecked則不會排除異常。編譯器預設是關閉溢位檢查的unchecked。若要開啟溢位檢查,使用 checked 在vs的專案屬性中也可設定開啟與否。也可以給一段 新增這樣的標記。如果這段 中呼叫了另外乙個方法,這個方法是不受這個標記控制的。sys...
第5章 基元型別 引用型別與值型別 (2)
所有的值型別都繼承自system.valuetype,而system.valuetype繼承自system.object。它重寫了system.object中的equals方法和gethashcode方法。當定義自己的值型別時我們也應重寫equals方法和gethashcode方法,為它們提供乙個顯...