參考資料 [1] 毛星雲【《Effective C 》提煉總結】 https://zhuanlan.zhihu.com/p/24553860 [2] 《C 捷徑教程》 [3] 什麼時候使用值類型?什麼時候使用引用類型? https://www.cnblogs.com/LittleFeiHu/p/44 ...
參考資料
[1] 毛星雲【《Effective C#》提煉總結】 https://zhuanlan.zhihu.com/p/24553860
[2] 《C# 捷徑教程》
[3] 什麼時候使用值類型?什麼時候使用引用類型?
https://www.cnblogs.com/LittleFeiHu/p/4489099.html
[4] 深入理解Java記憶體 https://www.cnblogs.com/lipeineng/p/8358601.html
[5] 棧記憶體 https://baike.baidu.com/item/%E6%A0%88%E5%86%85%E5%AD%98/8596201
基礎知識
- 在C#中,用struct創建的是值類型,繼承於System.ValueType,class創建的類是引用類型,繼承於System.Object。
疑難解答
值類型與引用類型的區別
值類型是封閉類型,無法繼承任何類(但可以實現介面),而引用類型則可以實現多態
值類型在充當函數參數、賦值時,傳遞的是值類型的副本,而引用類型則是傳遞的是對象的指針。《C# 捷徑編程》對這個的描述是下麵這樣的:
這意味著每個引用類型的變數事實上包括應該指向堆上的對象的引用(或者,如果當時還沒有引用對象的話,就是null)。當複製一個引用類型變數的值到另一個引用類型變數時,就創建了另一個指向同一對象的引用。
引用類型預設值是null,而值類型的預設值是其所定義的預設值(如int、float的預設值是0)。
引用類型必須用new關鍵字新建,而值類型則不必須,但如果要調用值類型中的方法(如簡單的get、set屬性),那麼必須使用new關鍵字生成值類型。
在記憶體中,值類型一般分配線上程棧上,不受GC(垃圾回收器)管理,當離開了該值類型的作用域後,會自動釋放(參考局部變數)。而引用類型一般分配在托管堆上,由GC負責釋放。
值類型複製說明
可以看到值類型的複製是完全複製一個副本給另一個變數,而引用類型則是將指向對象的指針賦給變數,所以引用類型的賦值,本質還是同一個對象。下麵上一段代碼進行說明。
struct Value {
public int a, b;
public override string ToString() {
return string.Format("[a:{0},b:{1}]",a,b);
}
}
class ValueRefer{
public int a, b;
public override string ToString() {
return string.Format("[a:{0},b:{1}]", a, b);
}
}
public class MainProgram {
public static void Main(string[] args) {
Value value1 = new Value();
Value value2 = value1;
ValueRefer valueRefer1 = new ValueRefer();
ValueRefer valueRefer2 = valueRefer1;
value2.a = 10;
valueRefer2.a = 10;
Console.WriteLine(string.Format("value1:{0}\nvalueRefer1:{1}\nvalue2:{2}\nvalueRefer2:{3}",value1,valueRefer1,value2,valueRefer2));
}
}
運行結果:
value1:[a:0,b:0]
valueRefer1:[a:10,b:0]
value2:[a:10,b:0]
valueRefer2:[a:10,b:0]
可以看到更改Value2的值不影響Value1,而更改ValueRefer2的值則會影響到ValueRefer1。
用一個交換的例子也能說明這個問題。請看如下代碼,對值類型和引用類型的a、b屬性進行一次交換。
struct Value {
private int b;
private int a;
public int A { get => a; set => a = value; }
public int B { get => b; set => b = value; }
public override string ToString() {
return string.Format("[a:{0},b:{1}]",A,B);
}
}
class ValueRefer{
private int b;
private int a;
public int A { get => a; set => a = value; }
public int B { get => b; set => b = value; }
public override string ToString() {
return string.Format("[a:{0},b:{1}]", A, B);
}
}
public class MainProgram {
public static void Main(string[] args) {
Value value = new Value();
value.A = 5;
value.B = 10;
ValueRefer valueRefer = new ValueRefer();
valueRefer.A = 5;
valueRefer.B = 10;
Console.WriteLine(string.Format("value:{0}\nvalueRefer:{1}", value, valueRefer));
// 交換值類型內屬性a、b的值
Swap(value);
// 交換引用類型內屬性a、b的值
Swap(valueRefer);
Console.WriteLine(string.Format("\nvalue:{0}\nvalueRefer:{1}",value,valueRefer));
}
static void Swap(Value value) {
int temp = value.A;
value.A = value.B;
value.B = temp;
}
static void Swap(ValueRefer value) {
int temp = value.A;
value.A = value.B;
value.B = temp;
}
}
運行結果如下:
value:[a:5,b:10]
valueRefer:[a:5,b:10]
value:[a:5,b:10]
valueRefer:[a:10,b:5]
可以看到引用類型的屬性被交換了,而值類型則沒有受影響,這說明瞭傳給函數的只是值類型的副本,而非其本體。
值類型和引用類型記憶體分配情況
首先,可以明確的是,值類型一般都分配線上程棧上(並不總是,有時也可作為欄位嵌入到引用類型的對象中),而引用類型的記憶體則必須從托管堆分配。在有些情況下,值類型可以提供更好的性能,這是由於它的記憶體從棧上分配。對於棧記憶體,百度百科的解釋如下:
棧的優勢是,存取速度比堆要快,僅次於寄存器,棧數據可以共用。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變數(,int, short, long, byte, float, double, boolean, char)和對象句柄。棧有一個很重要的特殊性,就是存在棧中的數據可以共用
值類型與Null的關係
首先值類型永遠不能賦值為null,因為值類型的值就是它本身。而對於引用變數來說,它的值則是對一個對象的引用,故可以用null(空引用)對其賦值。
大佬這個講的很好 https://www.cnblogs.com/murongxiaopifu/p/4842375.html 。。。本菜雞實在不知道如何歸納總結了~
概括一下就是,在實際編程中,可能需要讓值類型的變數的值既不是負數也不是0,而是真正不存在。在這種情況下,可以使用可空類型來對值類型的空值進行表示。
何時使用值類型何時使用引用類型
值類型有時可以提供更好的性能,而引用類型則是我們習慣用的。那麼如何權衡一個類應該為哪個類型呢?
根據參考資料[1][3]兩位大佬的說法,只有當一個類型滿足以下所有條件,我們才考慮是否將該類型聲明為值類型。
- 類型不需要從其他類型繼承,也不派生出其他任何類型
- 該類型的主要職責在於數據存儲嗎?
- 該類型的公有介面都是由訪問其數據成員的屬性定義的嗎?
在滿足上述條件的情況下,還必須滿足以下任意條件:
- 類型的實例較小
- 類型的實例較大且不作為方法參數傳遞
這是因為值類型在方法中充當方法參數傳遞時,是將值類型中所有欄位進行複製的,這會對性能造成影響。