基元類型
基元類型:編譯器直接支持的數據類型(char、byte、short、uint、long...),它直接映射到FCL(Framework類庫)中存在的類型。如int
對應的是System.Int32
。
基元類型之間支持隱式或者顯示轉型,支持寫成文本常量Console.WriteLine(555.ToString());
checked和unchecked開啟和關閉溢出檢查
//關閉溢出檢查
uint invalid = unchecked((uint)-1);
//vs默認關閉溢出檢查
int number = 1;
invalid = (uint)(number - 2);
number = -1;
//溢出檢查
invalid = checked((uint)number);
全局檢查設置:visual studio解決方案右鍵“屬性”選項-先擇“生成”選項-點擊“高級”按鈕-“高級設置”窗口中即可設置;在編寫代碼的時候可以打開全局檢測開關,雖然影響性能,但是可以有效的檢測到一些bug,等到發布程序的時候再關閉全局檢測。
引用類型和值類型
引用類型
所有稱為“類”的類型都是引用類型
- 內存必須從托管堆中分配
- 堆上分配的每個對象都有一些額外的成員,這些成員需要初始化
- 對象中的其他字節總是設置為0
- 從托管堆中分配一個對象時,可能強制執行GC(垃圾回收)操作
值類型
除了基元類型外,結構和枚舉也屬于值類型;所有值類型都是派生至抽象類型System.ValueType
- 值類型的實例在線程棧上分配(即使被被當作字段嵌入到引用類型對象中)
- 值類型的實例不受垃圾回收器的控制,減少了應用程序的垃圾回收次數
值類型雖然在性能上優于引用類型,但是依然存在一些問題,如裝箱和拆箱、無法繼承和派生其他類型(可以繼承接口)。
將值類型的變量賦值給另一個值類型變量,會執行逐個字段的復制;而對于引用類型來說只需復制內存地址;即,引用類型可以多個變量指向同一個對象,而值類型的變量是自成一體的對象,所以對一個值類型操作不可能影響另一個值類型變量。
值類型的裝箱和拆箱
裝箱
將值類型轉換成一個引用類型,以下是一個裝箱內部發生的事情
1.在托管堆中分配好內存。分配的內存是值類型各個字段的所需的內存加上托管堆上的兩個額外成員(同步塊索引和類型對象指針)所需的內存量。
2.值類型的字段復制到新分配的堆內存。
3.返回對象的地址。這個地址就是對象的引用
struct Point
{
public int x;
public int y;
}
class Program
{
static void Main(string[] args)
{
Point p;
p.x = 1;
p.y = 2;
object obj = p;
}
}
拆箱
拆箱不是裝箱的逆過程,它比裝箱簡單得多了,只是從已裝箱的對象中獲取它的各個字段的地址;但是完成拆箱之后一般還接著將這些堆中的值復制到線程棧的過程
p = (Point)obj;
通過ILDasm.exe工具查看IL代碼,如果代碼中含有box就說明這個部分發生裝箱,如果是unbox即為拆箱
未裝箱的值類型之所以比引用類型“輕量級”,主要是因為:
- 它們不需要在托管堆中分配內存
- 它們沒有堆上對象所需的額外成員(類型對象指針和同步塊索引)
由于未裝箱值類型沒有同步塊所引,所以不能對他們使用C#的lock,讓多個線程同步訪問同一實例
對象相等性和同一性
System.Object的虛方法Equals的作用是在兩個對象包含相同的值的情況下返回true;但是如果實參引用的對象不同,就會返回false,這實際上是同一性(identity),并非相等性
由于類型可以重寫Object的Equals方法,所以比較兩個對象的同一性,可以使用Object的ReferenceEquals。
對象哈希碼
System.Object提供了虛方法GetHashCode,它獲取任意對象的Int32哈希碼,如果定義一個類型重寫Equals方法,那么還應該重寫GetHashCode,這是應為兩個相等的對象,必須具有相同的哈希碼。
計算類型實例哈希碼的幾條規則:
- 算法提供良好的隨機分布,是哈希碼獲得最佳性能
- 可以調用基類的GetHashCode方法,并包含它的返回值;但是不要調用Object和ValueType的GetHashCode方法,應為它們的算法性能不是高效的
- 算法至少一個實例字段
- 算法中使用的字段是不可變的
- 包含相同值的不同對象應返回相同的哈希碼
dynamic基元類型
類型安全的編程語言的優勢:
- 編譯時可以檢測到對象的類型,避免不必要的錯誤
- 生成的代碼更小、更快,因為在編譯時進行了更對的假設,并在生成的IL和元數據體現出來
為了方便開發人員使用反射或與基本組件通信,C#允許將一個表達式的類型標記為dynamic。
代碼使用dynamic表達式/變量來調用一個成員時,編譯器會生成特殊的IL代碼(payload),在運行時,payload根據當前由dynamic表達式/變量引用的對象的實際類型來決定具體執行的操作。
dynamic和var的區別
- 用var聲明的局部變量只是一種簡化語法,它要求編譯器根據表達式推斷數據的實際類型
- var只能用于方法內部的局部變量,而dynamic可以用于方法內部的局部變量、字段和參數
- 表達式不能轉型為var,但是可以轉型為dynamic
- 必須顯示初始化var聲明的變量,但dynamic聲明的變量無需初始化
C#內建的動態求值功能所產生的額外開銷是不容忽視的,雖然動態語言簡化了語法,但是也要看是否值得,所以dynamic切勿濫用
參考:CLR via C#
文章列表