程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#基礎知識 >> C#基元類型、引用類型和值類型

C#基元類型、引用類型和值類型

編輯:C#基礎知識

 基元類型和FCL類型

FCL類型就是指Int32這種類型,這是CLR支持的類型。

而基元類型就是指int這種類型,這是C#編譯器支持的,實際上在編譯後,還是會被轉為Int32類型。

而且學過C的朋友肯定記得,int在32位機器和64位機器字節數可能不同,但是C#.NET裡int就是表示Int32。

因為在基元類型和FCL類型之間,有一個一一對應的映射關系。另外注意dynamic實際上對應的類型就是Object,只是說C#編譯器允許用簡單的語法讓dynamic變量參與動態調度。

表達式由字面量構成,編譯器在編譯的時候就能完成表達式求值

Boolean found=false;//生成的代碼將found設為0
Int32 x=100+20+3;//x設為123
String a="a"+"bc";//s設為“abc”

checked和unchecked基元類型操作

此指令就用來檢查溢出和不檢查溢出,而默認是unchecked,不過這個可以改。檢查溢出就報異常,不檢查溢出就回滾。

引用類型和值類型

所謂值類型的去看它們類型的定義,比如Int32和DayOfWeek都是struct和enum類型,而struct類型實際上派生自System.ValueType類型,而Enum類型派生自System.Enum類型。而Enum類型最終還是派生自System.ValueType類型。

好吧,他們倆個的差異性其實還是蠻多的,不過基本上這是最基礎的了,而且基本上是本書就講,所以反而懶得寫了。

裝箱與拆箱

裝箱就是把本來在棧中的值類型,在堆中新開辟一個內存空間,把值類型的數據復制進去,並增加引用類型都有的類型指針和同步塊索引,然後返回這個內存空間引用地址。

拆箱就是反過來,先獲取裝箱對象中各個字段的地址,再將這些字段包含的值從堆復制到棧。

由上面看出裝箱拆箱其實很影響效率,所以寫代碼的時候應該避免。

另外裝箱的值類型,調用自身的函數修改自己的字段的時候並不會修改堆裡的數據,只會先轉換為值類型,再修改棧裡的數據。

如果要修改堆裡的數據,只能定義一個接口,讓值類型裡的函數去實現這個接口,然後想修改堆裡的數據,那麼就把對象轉換為這個接口,那麼去修改的話,就會修改堆裡的數據。

如果你看不懂上面的話,那麼請慎用值類型,作者推薦不要定義任何會修改實例字段的屬性或者方法,甚至可以將值類型的所有字段都加上readonly。

其實結構體什麼的用類就好了,大的結構體傳參啊什麼的,搞不好還會引起棧溢出。畢竟每個線程也就1MB的棧空間。

對象的相等性和同一性

之前我們講過System.Object提供了名為Equals的虛方法,也就是說所有的對象都是有的,作用實在兩個對象包含相同值的前提下返回true。

然而這個方法只是比較了同一性,而不是相等性。

實際上就是對應的同一性就是指兩個對象的引用相同,也就是說它們指向同一個對象。

相等性如字面意思可知。也就說如果具備同一性,那麼一定具備相等性。

由於Equals是個虛方法,可以重寫,所以並不一定就是這個用法。(System.ValueType就重寫了,Equals實現的是相等性而不是統一性。但是這個Equals是裡的實現步驟用到了反射,而反射這個東西又是比較慢的,所以定義自己的值類型時可以考慮重寫,從而提高性能。)

而==這個操作符也是可以重載的,除非你在比較之前,將兩個對象的類型都轉換為object。

於是Object又提供了一個靜態方法,Object.ReferenceEquals,效果就如上所述,比較同一性。

注意,如果自己去定義一個值類型,然後重寫Equals方法去實現相等性。那麼應該注意讓類型實現IEquatable<T>接口的Equals方法(通常實現的Equals方法除了調用自己的類型參數,還應該有一個重載函數調用object參數,以便在內部調用類型安全的Equals方法。這個定義接收object對象的重寫函數麼就是對IEquatable<T>的Equals的實現),還有重寫==和!=操作符方法。

考慮到排序,所以可能還需要實現IComparable的CompareTo方法,和IComparable<T>的類型安全的CompareTo方法。實現了這些方法,那麼<,<=,>,>=在內部調用類型安全的CompareTo方法也OK了。

(如果你覺得上面自己定義值類型的實現還有什麼地方覺得遺漏,最好的方法其實就是去看int類型的定義就ok了)

對象哈希碼

System.Object提供了虛方法GetHashCode,它能獲取任意對象的Int32哈希碼。

另外重寫了Equals方法,那麼最好重寫GetHashCode方法。

因為在System.Collections.Hashtable類型和System.Collections.Generic.Dictionary類型以及其它的一些集合的實現中,要求兩個對象必須要有相同的Hash碼才被視為相等。

所以重寫Equals方法實現相等性後,最好也重寫GetHashCode方法,以確保相等型算法和對象哈希碼算法一致。

另外重寫時可以調用基類的GetHashCode方法,但是不要調用Object或者ValueType的GetHashCode方法,因為兩者的實現性能不高。

包含相同值的兩個不同對象應返回相同的哈希碼。(作者建議不要對哈希碼進行持久化,因為哈希碼的算法可能會改變)

dynamic基元類型

dynamic基元類型是為了方便開發人員使用反射或者與其它非.net組件通信.

代碼使用dynamic表達式/變量時,編譯器生成特殊的IL代碼來描述這種操作。這種特殊的代碼被稱為payload(有效載荷)。在運行時,payload根據dynamic表達式/變量引用的對象的實際類型來決定具體執行的操作。

dynamic類型在編譯後實際上是作為System.object,然而它在元數據中被應用了System.Runtime.CompilerServices.DynamicAttribute的實例。局部變量除外,因為Attribute顯然不能在方法內部使用。

另外使用的泛型的dynamic的代碼時,泛型代碼已經變異好了,將類型視為Object,編譯器不在泛型代碼中生成payload,所以也不會執行動態調度。

且編譯器允許使用隱式轉型語法,將表達式從dynamic轉型為其它類型。

dynamic a=123;
Int32 b=a;

另外dynamic表達式的求值結果也是一個dynamic類型。

不能定義對dynamic進行擴展的擴展方法,不能將lambda表達式或匿名方法作為實參傳給dynamic使用。

為COM對象生成可由“運行時”調用的包裝時。Com組件的方法中使用任何Variant實際都轉化為dynamic,這稱為動態化。顯著簡化了與COM對象的操作。

當然用dynamic會有額外的性能開銷,因為會引用一些必須的dll,然後執行一些動態綁定啊什麼的。如果只是一兩處用這個東西,還是用傳統方法好一點。(一般會引用Microsoft.CSharp.dll,與com組件操作還會用到System.Dynamic.dll)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved