C# 中的值类型和引用类型
C# 中所有的数据类型不是值类型就是引用类型。本文就这两种类型在变量定义、赋值、和作为函数参数传递时的不同之外作简单讨论。
值类型是使用对象实际值来表示对象的数据类型。 如果向一个变量分配值类型的实例,则该变量将被赋以该值的全新副本。
引用类型是使用对对象实际值的引用(类似于指针)来表示对象的数据类型。 如果为某个变量分配一个引用类型,则该变量将引用(或指向)原始值。 不创建任何副本。(来自MSDN)
值类型包括:
- 所有的数值类型
- Boolean、Char 和 DateTime
- 所有的结构和枚举
引用类型包括:
- String
- 数组
- 类、委托和接口
引用类型和值类型有不同的编译时规则和不同的运行时行为。
对于引用类型,创建对象时,将在堆上分配内存,变量只保存该对象的引用(类似于指针)。如 UriBuilder 是 System 命名空间中定义的一个类,Point 是 System.Drawing 命名空间中定义的一个结构。定义如下变量:
Point myPoint1 = new Point(20, 10); // Point 是一个结构 UriBuilder myUri1 = new UriBuilder("http://www.example.com"); // UriBuilder 是一个类
在上面的第一行代码中 CLR 将为变量 myPoint1 分配一块内存空间,而在第二行代码中 CLR 将要分配两块内存空间,一块存储 UriBuilder 对象,一块存储该对象的引用(myUri1)。第二行代码等价于:
UriBuilder myUri1; // CLR 为 UriBuilder 对象的引用 myUri1 分配一块内存空间 myUri1 = new UriBuilder("http://www.example.com"); // CLR 为 UriBuilder 对象分配一块内存空间
如果将上面定义的对象赋值给新的变量:
Point myPoint2 = myPoint1; UriBuilder myUri2 = myUri1;
myPoint2 是一个结构,将被赋以 myPoint1 的一个全新的副本。而 myUri2 是一个类,只被赋以对 UriBuilder 对象的引用,myUri1 和 myUri2 都引用同一对象。
在 C# 中函数的参数默认是按值传递的,即当向函数传递参数时传递的是参数的隐式的副本。对于值类型的参数,传递的是该值的一个副本,对于引用类型的参数,传递的是该引用的一个副本。例如:
Point point = new Point(20, 5); // 值类型变量 UriBuilder myUri = new UriBuilder("http://www.example.com"); // 引用类型变量 Test(point, myUri); // Test 为下面定义的一个方法 void Test(Point p, UriBuilder uri) { p.X = 10; // 不会影响 point,因为 p 是 point 的一个副本 uri.Host = "www.example.org"; // 会影响 myUri因为 uri 和 myUri 引用的是同一对象 uri = null; // 不会影响 myUri }
将 uri 设置为 null 不会产生任何影响,因为 uri 只是 UriBuilder 对象引用的一个副本。
我们可以使用 ref 修饰符改变参数的传递方式,按引用传递时函数直接与调用者的参数交互。例如:
Point point = new Point(20, 5); // 值类型变量 UriBuilder myUri = new UriBuilder("http://www.example.com");// 引用类型变量 Test(ref point, ref myUri); // Test 是下面定义的函数 void Test(ref Point p, ref UriBuilder uri) { p.X = 10; // 将会改变 point 的 X 值 uri.Host = "www.example.org"; // 将会改变 myUri 的 Host 值 uri = null; // 将会把 myUri 设置为 null }