在FCL中,所有称为“结构”(struct)的类型都是值类型,所有称为“类”(class)的类型都是引用类型。所有的Struct都直接派生自抽象类System.ValueType,而System.ValueType直接从System.Object派生。所有的枚举都直接从System.Enum派生,而后者又派生自System.ValueType,所以枚举也是值类型。由于CLR的单继承规则,所以我们在定义值类型时,不能指定基类型,但可以实现接口。同时从下图生成的IL也可以看出,值类型是隐式密封的(sealed),也就是说也不能从值类型派生。
虽然引用类型与值类型实质只是内存分配上的差异,但这种差异会导致两种类型在行为表现上有着明显不同,比如下面的例子:
struct ValType { public int x;} class RefType { public int x;} class Program { static void Main(string[] args) { ValType v1 = new ValType(); //在栈上分配内存 RefType r1 = new RefType(); //在堆上分配内存 v1.x = 2; r1.x = 2; //执行到这里,内存结构请见图1 Console.WriteLine(v1.x); //2 Console.WriteLine(r1.x); //2 ValType v2 = v1; //在栈上分配内存(v2),并把v1栈的内容复制到v2 RefType r2 = r1; //把r1的堆地址复制给r2 v2.x = 5; //只改变v2栈的内容 r2.x = 5; //由于r2和r1都引用同一个堆上的对象,改变r2也会改变r1 //执行到这里,内存结构请见图2 Console.WriteLine(v1.x); //2 Console.WriteLine(r1.x); //5 注意这里变成了r2修改后的值 Console.WriteLine(v2.x); //5 Console.WriteLine(r2.x); //5 Console.ReadKey(); } }
首先我们定义一个一值类型与一个引用类型,内部都只有一个字段。用new操作符分配内存时,值类型v1的内存分配在了线程栈上,引用类型r1的内存分配在了托管堆上,在程序运行到第一次WriteLine输出时,看到的结果是一致的。但接下来声明两个新的对象并执行赋值时,这里的发生的事明显不同:虽然赋值操作都是拷贝线程栈上变量的内容,但由于值类型变量v1的栈内容就是ValType类型实例本身,而引用类型r1的栈内容是RefType对象实例在堆上的地址。所以赋值后的结果就是,v1和v2各保存了一份ValType类型实例,而r1和r2保存了同一块堆内存的地址。所以改变r2对象导致了r1对象的随同改变。下面是内存示意图:
图1
图2
虽然值类型实例不需要垃圾回收,但由于值类型在传递时,传递的是内容本身,所以并不适合将所一些实例较大的类型定义为值类型。实现上除非满足以下所有条件,否则不应该将一个类型声明为值类型。
- 没有更改其字段的成员,即该类型是不可变的。(建议所有字段为readonly)
- 类型不需要从其他任何类型继承。(值类型不能选择基类)
- 类型也不会派生出其他任何类型。(所有的值类型都是隐式密封sealed的)
- 实例较小(约
关注打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?


微信扫码登录