参考资料
[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]
可以看到引用类型的属性被交换了,而值类型则没有受影响,这说明了传给函数的只是值类型的副本,而非其本体。
值类型和引用类型内存分配情况
首先,可以明确的是,值类型一般都分配在线程栈上(并不总是,有时也可作为字段嵌入到引用类型的对象中),而引用类型的内存则必须从托管堆分配。在有些情况下,值类型可以提供更好的性能,这是由于它的内存从栈上分