值类型和引用类型
在 C# 中,这些数据类型根据它们在内存中存储值的方式进行分类。C# 包括以下数据类型类别
- 值类型
- 引用类型
- 指针类型
值类型
如果数据类型在其自己的内存空间中存储数据值,则它是一个值类型。这意味着这些数据类型的变量直接包含值。

所有值类型都派生自 System.ValueType
,而 System.ValueType
又派生自 System.Object
。例如,考虑整数变量 int i = 100;
系统将 100
存储在为变量 i
分配的内存空间中。下图说明了 100
如何存储在内存中某个假设位置 (0x239110
) 用于 'i'

以下数据类型都是值类型
- bool
- byte
- char
- decimal
- double
- enum
- float
- int
- long
- sbyte
- short
- struct
- uint
- ulong
- ushort
传递值类型变量
当您将值类型变量从一个方法传递到另一个方法时,系统会在另一个方法中创建变量的单独副本。如果在一个方法中更改了值,它不会影响另一个方法中的变量。
static void ChangeValue(int x)
{
x = 200;
Console.WriteLine(x);
}
static void Main(string[] args)
{
int i = 100;
Console.WriteLine(i);
ChangeValue(i);
Console.WriteLine(i);
}
200
100
在上面的示例中,即使我们将 Main()
方法中的变量 i
传递给 ChangeValue()
方法并在其中更改其值,它也保持不变。
引用类型
与值类型不同,引用类型不直接存储其值。相反,它存储值被存储的地址。换句话说,引用类型包含指向保存数据的另一个内存位置的指针。
例如,考虑以下字符串变量
string s = "Hello World!!";
下图显示了系统如何为上述字符串变量分配内存。

如上图所示,系统为变量 s
在内存中选择一个随机位置 (0x803200)
。变量 s
的值是 0x600000
,它是实际数据值的内存地址。因此,引用类型存储实际值存储的地址,而不是值本身。
以下是引用数据类型
- 字符串
- 数组(即使它们的元素是值类型)
- 类
- 委托
传递引用类型变量
当您将引用类型变量从一个方法传递到另一个方法时,它不会创建新副本;相反,它传递变量的地址。因此,如果我们在方法中更改变量的值,它也会反映在调用方法中。
static void ChangeReferenceType(Student std2)
{
std2.StudentName = "Steve";
}
static void Main(string[] args)
{
Student std1 = new Student();
std1.StudentName = "Bill";
ChangeReferenceType(std1);
Console.WriteLine(std1.StudentName);
}
在上面的示例中,我们将 Student
对象 std1
传递给 ChangeReferenceType()
方法。这里,它实际上传递了 std1
的内存地址。因此,当 ChangeReferenceType()
方法更改 StudentName
时,它实际上是在更改 std1
对象的 StudentName
,因为 std1
和 std2
都指向内存中的相同地址。
字符串是引用类型,但它是不可变的。这意味着一旦我们赋值,就不能更改。如果我们更改字符串值,则编译器会在内存中创建一个新的字符串对象,并将变量指向新的内存位置。因此,将字符串值传递给函数将在内存中创建一个新变量,并且函数中值的任何更改都不会反映在原始值中,如下所示。
static void ChangeReferenceType(string name)
{
name = "Steve";
}
static void Main(string[] args)
{
string name = "Bill";
ChangeReferenceType(name);
Console.WriteLine(name);
}
空
未初始化时,引用类型变量的默认值为 null
。Null
表示不引用任何对象。

值类型变量不能为 null,因为它保存的是值,而不是内存地址。C# 2.0 引入了可空类型,您可以使用它将 null 分配给值类型变量,或声明一个值类型变量而不为其赋值。