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


Answers:


313

可变和不可变是英文单词,分别表示“可以更改”和“不能更改”。在IT环境中,单词的含义是相同的。即

  • 可变字符串可以更改,并且
  • 不变的字符串不能更改。

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


作为记录:

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

要在以C#表示的字符串上“实现更改” String,实际上需要创建一个新String对象。原稿String未更改...因为它不可更改。

在大多数情况下,最好使用String它们,因为这是更容易理解它们的原因。例如,您不需要考虑其他线程可能“更改我的字符串”的可能性。但是,当您需要使用一系列操作来构造或修改字符串时,使用可能会更有效StringBuilder


最后,对于那些断言a StringBuilder不是字符串,因为它不是不可变的人们,Microsoft 文档StringBuilder因此描述了:

“表示可变的字符串。不能继承此类。”


182

String 是一成不变的

即字符串不能更改。当您更改字符串(例如通过添加到字符串)时,实际上是在创建新字符串。

StringBuilder不是一成不变的(而是可变的)

因此,如果您必须多次更改字符串(例如多个串联),请使用StringBuilder


4
如果多次连接不可变的字符串会发生什么?没有引用的字符串的内存使用负载?还是慢?
鲁迪

13
@Rudie-都。每个串联都创建一个新的String对象,并复制两个输入字符串的所有字符。导致O(N^2)行为...
Stephen C

@StephenC用关键字解释。
肯特·廖

6
如果您必须多次更改字符串(例如多个串联),请使用StringBuilder ...这完全让我
扫兴了

45

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

不变的弦

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

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

因此,所有的操作,你喜欢上一个字符串进行SubstringRemoveReplace,使用串联“+”操作符等将创建一个新的字符串,并将其返回。

请参阅以下程序进行演示-

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

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

为了获得不变性好处以及为什么字符串不可变,请检查.NET字符串为什么不可变?

可变字符串

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

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


美丽的解释!
哈里

36

所有string对象在C#中都是不可变的。该类的对象string一旦创建,就不能代表其构造值以外的任何值。所有似乎“更改”字符串的操作都会产生一个新的字符串。这在内存方面效率低下,但是在能够相信字符串不会改变您的形式下非常有用-因为只要您不更改引用,被引用的字符串就永远不会改变。

相比之下,可变对象具有可以更改的数据字段。它的一个或多个方法将更改对象的内容,或者具有一个属性,当写入该属性时,该属性将更改对象的值。

如果您有一个可变的对象StringBuffer- 与String最相似的对象-那么如果要绝对确保它不会从您的身下变出来,则必须对其进行复制。这就是为什么可变对象危险用作任何形式Dictionary或集合的键的危险,因为对象本身可能会更改,并且数据结构无法知道,从而导致数据损坏,最终导致程序崩溃。

但是,您可以更改其内容-因此,由于要更改单个字符或类似字符,因此与创建完整副本相比,其内存效率要高得多。

通常,正确的做法是在创建对象时使用可变对象,并在完成后使用不可变对象。当然,这适用于具有不变形式的对象。大多数馆藏都没有。但是,在将集合的内部状态发送到其他上下文时,提供只读形式的集合通常很有用,这等效于不可变的形式;否则,某些事情可能会占用该返回值,对其执行某些操作并破坏您的返回值数据。


12

不变的:

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

可变的

对对象执行某些操作时,对象本身不会像StringBuilder那样修改任何新的对象


8

在.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

6

字符串是可变的,因为.NET在后台使用字符串池。它的意思是 :

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

name和name2都从字符串池引用了相同的内存位置。现在假设您要将name2更改为:

name2 = "My Loving Country";

它将在字符串池中查找字符串“ My Loving Country”,如果找到,则将获得其引用;否则,将在字符串池中创建其他明智的新字符串“ My Loving Country”,而name2将获得它的引用。但是整个过程“ My Country ”并没有改变,因为像name这样的其他变量仍在使用它。这就是为什么string是IMMUTABLE的原因。

StringBuilder以不同的方式工作,并且不使用字符串池。当我们创建StringBuilder的任何实例时:

var address  = new StringBuilder(500);

它为此实例分配大小为500字节的内存块,并且所有操作仅修改该内存位置,并且此内存不与任何其他对象共享。这就是StringBuilderMUTABLE的原因。

希望对您有所帮助。


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声明您得到的行为是不确定的。
史蒂芬·C

2

为了明确起见,在C#(或一般的.NET)中没有诸如可变字符串之类的东西。其他语言支持可变字符串(可以更改的字符串),但.NET框架不支持。

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

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

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


很抱歉,但对于微软的文档StringBuilder相矛盾的:看msdn.microsoft.com/en-us/library/...
斯蒂芬ç

1

数据值不能更改。注意:变量值可以更改,但是原始的不可变数据值被丢弃,并且在内存中创建了新的数据值。


1

实施细节。

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

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


0

来自http://yassershaikh.com/what-is-the-difference-between-strings-and-stringbuilder-in-c-net/

简短的回答:字符串是不可变的–而StringBuilder是可变的。

那是什么意思 ?Wiki说:在面向对象中,不可变对象是指其状态在创建后无法修改的对象。这与可变对象相反,可变对象可以在创建后进行修改。

从StringBuilder类文档中:

String对象是不可变的。每次使用System.String类中的方法之一时,都会在内存中创建一个新的字符串对象,这需要为该新对象分配新的空间。

在需要对字符串进行重复修改的情况下,与创建新的String对象相关的开销可能会非常昂贵。

当您要修改字符串而不创建新对象时,可以使用System.Text.StringBuilder类。例如,当将多个字符串串联在一起时,使用StringBuilder类可以提高性能。


0

这是不可变字符串和可变字符串构建器的示例

        Console.WriteLine("Mutable String Builder");
        Console.WriteLine("....................................");
        Console.WriteLine();
        StringBuilder sb = new StringBuilder("Very Good Morning");
        Console.WriteLine(sb.ToString());
        sb.Remove(0, 5);
        Console.WriteLine(sb.ToString());

        Console.WriteLine();

        Console.WriteLine("Immutable String");
        Console.WriteLine("....................................");
        Console.WriteLine();
        string s = "Very Good Morning";
        Console.WriteLine(s);
        s.Substring(0, 5);
        Console.WriteLine(s);
        Console.ReadLine();

如果您也可以提供输出,也将非常好@Gokul :)
Roy Lee

子字符串不会更改基础值。它仅返回一个值。

@Kye,为什么?因为字符串是不可变的:)
LuckyLikey

您的2行代码-:s.Substring(0,5); Console.WriteLine(s); 此处s.substring()不会更改s。此示例未显示string是否不可变。
Dhananjay 2015年

0

C#中的字符串是不可变的。如果将它与任何字符串连接,则实际上是在创建一个新字符串,即新的字符串对象!但是StringBuilder创建可变的字符串。


0

StringBuilder是连接大数据字符串的更好选择,因为StringBuilder是可变的字符串类型,而StringBuilder对象是不可变的类型,这意味着StringBuilder在连接字符串时永远不会创建对象的新实例。

如果我们使用字符串而不是StringBuilder实现串联,那么它将每次在内存中创建新实例。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.