更新:2007 年 11 月
与您可能熟悉的某些编程语言不同,C# 具有两种数据类型:值和引用。如果应用程序的性能很重要,或对 C# 管理数据和内存的方式感兴趣,对这种差异的了解就很重要。
如果一个变量是使用基本的内置数据类型之一或用户定义的结构进行声明的,则该变量为值类型。但 string 数据类型除外,它是引用类型。
值类型的内容存储在堆栈上分配的内存中。例如,在本例中,值 42 存储在称为“栈”的内存区域中:
int x = 42;
由于定义变量的方法结束执行而使变量 x 超出范围时,其值则从栈中丢弃。
使用栈效率较高,但值类型的生命周期有限,不适合在不同类之间共享数据。
相反,引用类型(例如,类或数组的实例)在另一个称为“堆”的内存区域中分配。在下面的示例中,构成数组的 10 个整数所需的空间是在堆上分配的。
int[] numbers = new int[10];
在方法完成时,并不将此内存归还给堆;仅当 C# 的垃圾回收系统确定不再需要该内存时,才进行回收。声明引用类型需要更多系统开销,但它们的优点是可以从其他类进行访问。
装箱和取消装箱
将值类型转换为引用类型的过程称为装箱。装箱某个变量,就是创建一个引用变量,它指向堆上的新副本。引用变量是一个对象,因此,它可以使用每个对象都继承的所有方法(例如 ToString())。下面的代码进行了具体的说明:
int i = 67; // i is a value type
object o = i; // i is boxed
System.Console.WriteLine(i.ToString()); // i is boxed
在使用旨在用于对象的类时将会遇到取消装箱:例如,使用 ArrayList 存储整数。将某个整数存储在 ArrayList 中,即是对整数进行装箱。检索某个整数时,必须对它进行取消装箱。
System.Collections.ArrayList list =
new System.Collections.ArrayList(); // list is a reference type
int n = 67; // n is a value type
list.Add(n); // n is boxed
n = (int)list[0]; // list[0] is unboxed
性能问题
让我们再深入探讨一下。数据传入方法作为值类型参数,即在堆栈上创建了每个参数的副本。很明显,如果相关参数是大型数据类型(例如,包含很多元素的用户定义结构),或多次执行方法,都可能影响性能。
在这些情况下,使用 ref 关键字向类型传递一个引用可能更为可取。C# 中的这种方法等效于 C++ 中将指向变量的指针传入函数的方法。对于 C++ 版本,该方法能够更改变量的内容,这不是始终安全的。程序员需要确定安全性和性能之间的平衡。
int AddTen(int number) // parameter is passed by value
{
return number + 10;
}
void AddTen(ref int number) // parameter is passed by reference
{
number += 10;
}
out 关键字类似于 ref 关键字,但它会告知编译器该方法必须为参数赋值,否则将产生编译错误。
void SetToTen(out int number)
{
// If this line is not present, the code will not compile.
number = 10;
}