字符串是值类型还是引用类型?

50

字符串是值类型还是引用类型?

我就是找不到一个“好”的解释……


在C#中,为什么字符串是一个行为类似值类型的引用类型? - nawfal
被接受的答案“证明”它是一个引用类型。与其他引用类型不同的是,当在以下情况下进行评估时,它不会抛出NullReferenceExceptionstring s0 = null; Console.WriteLine(s0); string result = String.Concat(s0, "aaa");也许它只是不太适合被承认的类别中。 - H2ONaCl
2
@H2ONaCl 在这些情况下不应该抛出异常;这些方法可以选择抛出ArgumentNullException,但是两种方法都明确表示他们不选择这样做;Console.WriteLine说:“如果值为null,则仅将行终止符写入标准输出流。”;String.Concat说“空字符串用于替换任何空参数。”。这很好。我们通常期望NullReferenceException的唯一时间是像s0.Length这样的东西,它确实会抛出异常。 - Marc Gravell
8个回答

95
Console.WriteLine(typeof(string).IsClass); // true

这是一个引用类型。

它不能是值类型,因为值类型需要在堆栈等地方有已知的大小。作为引用类型,即使字符串的大小未知,引用 的大小也是预先已知的。

由于它是不可变的,所以它的行为类似于你期望的值类型; 即一旦创建就不会更改。但是还有很多其他不可变的引用类型。例如Delegate实例。

* = 除了在 StringBuilder 内部,但在此期间您从未看到它…


6
我非常不喜欢小写的字符串"type." 它看起来像值类型,但行为却像引用类型。 - ojrac
10
这个案子跟它有什么关系呢?它只是一个别名...对象是引用类型,例如。 - Marc Gravell
6
StringBuilder 内部使用 char[],而不是字符串。 - user315772
3
@Phong 再次检查一下。上次我看到它确实是一个“字符串” - 然后进行了不安全的变异(需要时分配新字符串并复制)。你有检查过吗?如果有,那么是哪个 .net 版本? - Marc Gravell
3
@SMI string 的默认值是 null,这是因为 string 是一个引用类型。说 string 的默认值是 "" 是不正确的,因为它并不是。 - Marc Gravell
显示剩余5条评论

17


11

基本的“解释”是基于在“声明”变量时分配给这个变量的内存位置中实际存储的“内容”。“值类型”指的是如果该变量名所引用的内存位置中存储了该内容的实际值。

   int x;  // memory allocated to hold Value of x, default value assigned of zero

如果在“声明”变量时分配的内存槽只会保存一些其他内存地址,实际值(或值)将存储在那里,那么它就是引用类型。

   MyClass x;  // Memory allocated to hold an address, 
               // default address of null (0) assigned.  
               // NO MEMORY ALLOCATED for x itself 

或者,如果声明包括初始化:

   MyClass x = new MyClass();  
     // Now, Memory slot (call it Addr1) is allocated to hold address of x, 
     // more memory (call it Addr2) is allocated to hold a new MyClass object.
     // New MyClass object created, stored in memory Addr2 (on the Heap)
     // Address of new object (Addr2) is stored in Addr1

对于一个字符串,它是在堆上创建的,它的地址存储在为变量分配的内存空间中,因此它是一种引用类型。


6

字符串是一种不可变的引用类型,具有某些特性,使其偶尔表现出值类型的外观。


4

字符串类型代表了零个或多个Unicode字符的序列。在.NET Framework中,string是String的别名。

尽管string是引用类型,但相等运算符(==和!=)被定义为比较string对象的值而不是引用。这使得测试字符串相等更加直观。例如:

string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine((object)a == (object)b);

这段代码输出"True",然后是"False",因为字符串的内容相同,但a和b并不引用同一个字符串实例。

字符串是不可变的--在创建字符串对象之后,无法更改其内容,尽管语法似乎可以这样做。例如,当您编写此代码时,编译器实际上会创建一个新的字符串对象来保存新字符序列,并将该新对象分配给b。然后,字符串"h"就可以被垃圾回收了。

string b = "h";
b += "ello";

3
在许多编程语言中,有两种常见类型的东西:那些存储位置实际上会持有该类型对象的定义类型,以及那些存储位置会持有对存储在其他地方的该类型对象的引用的定义类型。此外,还有许多类型的语义可以提供:
1.不可变值语义:特定类型的实例具有某些特征(“值”),这形成了它们的身份基础。值相等的两个项可以互换使用,而不管它们存储在哪里。只要实例存在,值就是恒定的。此类类型的变量可以更改其值,但仅通过将不同的实例存储到变量中来实现。
2.不可变引用语义:一般类似于不可变值语义,但是在不同时间创建的两个实例将报告自己作为不同的实例。
3.可变值语义:特定类型的实例具有某些特征或一组特征(“值”),这形成了它们的身份基础,但是这些特征可以更改而无需替换整个实例。每个实例都存储在一个变量或字段中;将一个变量或字段复制到另一个变量或字段会将所有值从第一个实例复制到第二个实例,但实例仍然是分开的。将来对一个实例的更改不会影响另一个实例。
4.可变引用语义:每个实例都与自己相同,但与任何其他实体都不同,并且实例具有一个或多个可以在现有实例内更改的值。任意数量的变量或字段可以持有对任何实例的引用。将一个或多个变量或字段复制到另一个只是使第二个引用与第一个相同的实例。因此,对其中一个变量引用的实例进行的任何更改都将影响另一个变量引用的实例(即相同的实例)。
在一些编程语言中,例如C ++,直接存储和间接引用类型都可以实现上述四种语义之一。在.NET和C#中,具有公开字段的直接存储类型始终实现#3,具有公开字段的类类型始终实现#4,其他值类型可以实现上述任何一种语义,而其他引用类型可以实现#1、#2或#4,但不是#3,这是一个略微泄漏的抽象。字符串实现#1。

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接