C#中可变字符串和不可变字符串有什么区别?

202

在C#中,可变字符串和不可变字符串有什么区别?


2
可能是重复的问题 - https://dev59.com/snE95IYBdhLWcg3wUMLW - RameshVel
6
不完全相同。非常相似,但这与完全相同是不一样的。 - Marcelo Cantos
2
有关StringBuilder内部的相关问题 https://dev59.com/EHA65IYBdhLWcg3w6DNO - Brian Rasmussen
15个回答

330

Mutable和immutable是英文单词,分别表示“可以更改”和“不可更改”。在IT上下文中,这些单词的含义相同:

  • 可变字符串可以更改
  • 不可变字符串无法更改

这些单词在C#/.NET中的含义与其他编程语言/环境中的含义相同,尽管(显然)类型名称和其他细节可能不同。


记录一下:

  • String是标准的C#/.Net不可变字符串类型
  • StringBuilder是标准的C#/.Net可变字符串类型

要对表示为C# String的字符串进行“更改”,实际上需要创建一个新的String对象。原始的 String 没有被更改...因为它是不可更改的。

大多数情况下最好使用String,因为它更易于理解;例如,您不需要考虑某些其他线程可能会“更改我的字符串”的可能性。

但是,当您需要使用一系列操作构建或修改字符串时,使用 StringBuilder 可能更有效率。例如,在将许多字符串片段连接成较大的字符串时:

  • 如果按顺序连接String,则会复制O(N ^ 2)个字符,其中N是组件字符串的数量。
  • 如果使用StringBuilder ,则只复制O(N)个字符。

最后,对于那些断言 StringBuilder 不是字符串因为它不可变的人,Microsoft 文档这样描述StringBuilder

"表示一个可变的字符字符串。此类不能被继承。"


185

String 是不可变的。

也就是说,字符串不能被改变。当你改变一个字符串(例如添加内容),实际上是创建了一个新的字符串。

但是 StringBuilder 是可变的。

因此,如果你需要多次修改一个字符串,比如多次拼接,那么请使用 StringBuilder


4
如果您多次连接不可变字符串会发生什么?字符串的内存占用会增加很多,而没有任何引用吗?还是只会变得很慢? - Rudie
13
@Rudie - 两者都是。每次连接字符串都会创建一个新的字符串对象,并复制两个输入字符串的所有字符。这将导致O(N^2)的行为... - Stephen C
@StephenC用关键词解释了。 - Kent Liau
8
如果你需要多次更改一个字符串,例如多次连接操作,那么请使用StringBuilder。...这完全澄清了我的想法, 谢谢。 - katmanco
2
"完全解决了我的疑虑" - 你的意思是像“恢复出厂设置”一样吗? :-) - Stephen C

47

如果一个对象被创建后可以通过调用各种操作来改变其状态,则该对象是可变的;否则它是不可变的。

不可变的字符串

在C#(和.NET)中,字符串由 System.String 类表示。string 关键字是此类的别名。

System.String 类是不可变的,即一旦创建其状态就不能更改

因此,对字符串执行的所有操作(如 Substring、Remove、Replace、使用 '+' 操作符进行连接等)都会创建并返回一个新字符串。

请参见以下示例程序进行演示:

string str = "mystring";
string newString = str.Substring(2);
Console.WriteLine(newString);
Console.WriteLine(str);

这将分别打印'string'和'mystring'。

关于为什么字符串是不可变的以及不可变性的好处,请查看为什么.NET字符串是不可变的?

可变字符串

如果您想要经常修改的字符串,可以使用StringBuilder类。对StringBuilder实例的操作将修改同一对象。

有关何时使用StringBuilder的更多建议,请参见何时使用StringBuilder?


漂亮的解释! - Hari

36
所有的C#字符串对象都是不可变的。一旦创建了string类的对象,它们就不能表示除了构造时指定的值之外的任何值。所有看起来“更改”字符串的操作实际上都会生成一个新的字符串。这种方式虽然在使用内存方面效率较低,但是非常有用,因为只要不更改引用,被引用的字符串就永远不会改变。
相比之下,可变对象具有可更改的数据字段。其中一个或多个方法将更改对象的内容,或者它具有一个属性,当写入其中时,将更改对象的值。
如果你有一个可变对象-与String最相似的一个是StringBuffer-那么如果你想绝对确保它不会在你的控制下发生改变,你必须对其进行复制。这就是为什么可变对象在作为任何形式的Dictionary或集合键时都是危险的原因-对象本身可能会发生变化,而数据结构无法知道,导致数据损坏,最终导致程序崩溃。
然而,你可以更改它的内容-所以它比完全复制要更节省内存,因为你只想更改一个字符或类似的东西。
通常情况下,在创建某些东西时使用可变对象,完成后使用不可变对象。当然,这适用于具有不可变形式的对象;大多数集合都没有。然而,在将集合的内部状态发送到其他环境时,提供只读的集合形式往往是有用的-否则,某些东西可能会获取该返回值,对其进行更改,并破坏你的数据。

12

Immutable:

当您对对象执行某些操作时,它会创建一个新对象,因此状态不可修改,就像字符串一样。

Mutable:

当您对对象执行某些操作时,对象本身被修改,而不是创建新的对象,就像StringBuilder一样。


7

在.NET中,System.String(也叫做字符串)是不可变对象。这意味着当您创建一个对象时,之后无法更改其值。您只能重新创建不可变对象。

System.Text.StringBuilder是System.String的可变等效项,您可以更改它的值。

例如:

class Program
{
    static void Main(string[] args)
    {

        System.String str = "inital value";
        str = "\nsecond value";
        str = "\nthird value";

        StringBuilder sb = new StringBuilder();
        sb.Append("initial value");
        sb.AppendLine("second value");
        sb.AppendLine("third value");
    }
}

生成以下 MSIL 代码: 如果你调查代码,你会发现每当你改变 System.String 对象时,实际上是创建了一个新的对象。但是在 System.Text.StringBuilder 中,每当你改变文本的值时,不会重新创建对象。
.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       62 (0x3e)
  .maxstack  2
  .locals init ([0] string str,
           [1] class [mscorlib]System.Text.StringBuilder sb)
  IL_0000:  nop
  IL_0001:  ldstr      "inital value"
  IL_0006:  stloc.0
  IL_0007:  ldstr      "\nsecond value"
  IL_000c:  stloc.0
  IL_000d:  ldstr      "\nthird value"
  IL_0012:  stloc.0
  IL_0013:  newobj     instance void [mscorlib]System.Text.StringBuilder::.ctor()
  IL_0018:  stloc.1
  IL_0019:  ldloc.1
  IL_001a:  ldstr      "initial value"
  IL_001f:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
  IL_0024:  pop
  IL_0025:  ldloc.1
  IL_0026:  ldstr      "second value"
  IL_002b:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine(string)
  IL_0030:  pop
  IL_0031:  ldloc.1
  IL_0032:  ldstr      "third value"
  IL_0037:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine(string)
  IL_003c:  pop
  IL_003d:  ret
} // end of method Program::Main

5
哦,这个反汇编代码只是展示指针分配和方法调用,几乎与可变性无关。我不明白为什么有些人认为反汇编一些无关的示例代码会帮助人们理解这种事情。首先,大多数程序员并不熟练掌握CLI代码。 - Stephen C

5

字符串是可变的,因为.NET在幕后使用了字符串池。这意味着:

string name = "My Country";
string name2 = "My Country";

name和name2均指向字符串池中相同的内存位置。 现在假设您想要将name2更改为:

name2 = "My Loving Country";

它会在字符串池中查找字符串"My Loving Country",如果找到,则将返回它的引用;否则,将在字符串池中创建新的字符串"My Loving Country",并将其引用赋给name2。但是,如果像name这样的其他变量仍在使用它,那么整个过程中"My Country"就不会被改变。这就是字符串为什么是不可变的原因。
StringBuilder的工作方式不同,它不使用字符串池。当我们创建任何StringBuilder实例时:
var address  = new StringBuilder(500);

该实例分配了一个大小为500字节的内存块,并且所有操作都只修改该内存位置,该内存不与任何其他对象共享。这就是为什么StringBuilder是可变的原因。
希望这能有所帮助。

5

实际上没有。String类是可变的。

unsafe
{
    string foo = string.Copy("I am immutable.");
    fixed (char* pChar = foo)
    {
        char* pFoo = pChar;

        pFoo[5] = ' ';
        pFoo[6] = ' ';
    }

    Console.WriteLine(foo); // "I am   mutable."
}

这种逻辑在String和StringBuilder类中经常使用,它们每次调用Concat、Substring等方法时都会分配一个新的字符串,并使用指针算术将其复制到新字符串中。字符串不会自己发生变化,因此被认为是“不可变”的。
顺便说一下,不要尝试对字符串字面量进行这样的操作,否则会严重破坏程序:
string bar = "I am a string.";

fixed (char* pChar = bar)
{
    char* pBar = pChar;

    pBar[2] = ' ';
}

string baz = "I am a string.";

Console.WriteLine(baz); // "I  m a string."

这是因为在桌面.NET Framework中,字符串字面量被“内部化”了;换句话说,barbaz指向完全相同的字符串,所以改变一个将会改变另一个。然而,如果你正在使用像WinRT这样的非托管平台,它缺乏字符串内部化功能,这一点就不那么美好了。

1
是的。如果您通过破解应该关闭的抽象来打破规则,那么几乎没有什么是不可变的。您可以使用恶劣的反射在Java中执行类似的操作...但是JLS声明您获得的行为是未定义的。 - Stephen C

1

在实现细节方面。

CLR2的System.String是可变的。 StringBuilder.Append调用String.AppendInplace (私有方法)

CLR4的System.String是不可变的。 StringBuilder具有分块的Char数组。


1

澄清一下,在C#(或.NET框架)中,不存在可变字符串的概念。其他语言支持可变字符串(可以更改的字符串),但.NET框架不支持。

因此,对于您的问题,正确答案是C#中所有字符串都是不可变的。

字符串具有特定的含义。小写关键字"string"只是从System.String类实例化的对象的快捷方式。从string类创建的所有对象始终是不可变的。

如果您想要一个可变的文本表示,则需要使用另一个类,如StringBuilder。 StringBuilder允许您迭代地构建“单词”集合,然后将其转换为字符串(再次是不可变的)。


抱歉,但是StringBuilder的微软文档与此相矛盾:请参见https://msdn.microsoft.com/zh-cn/library/system.text.stringbuilder(v=vs.110).aspx。 - Stephen C

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