如果在C#中字符串是不可变的,那我为什么可以这样做?

10

我今天看到,C#中的字符串是不可变的,一旦创建就无法更改,那么下面的代码为什么会起作用呢?

string str="a";
str +="b";
str +="c";
str +="d";
str +="e";

console.write(str) //output: abcde

为什么变量的值会改变?

8个回答

16

字符串对象是不可变的,但变量可以被重新分配。

你创建了单独的对象

a
ab
abc
abcd
abcde
每个不可变字符串都按顺序被赋值给变量str。
您无法更改字符串内部的内容(即字符)。
改变变量是完全不同的事情。

1
这个例子实际上在内存中创建了10个字符串。"", "a", "b", "c", "d", "e", "ab", "abc", "abcd"和"abcde"。这就是为什么最好使用StringBuilder来连接字符串或String.Format()当您想要将一个字符串的值插入到另一个字符串中时。 - nekno
6
@nekno 我反对“最好使用StringBuilder”的观点。虽然有可能出现情况,其中StringBuilder会更快(例如用于模板生成,这是它非常适合的情况),但它并不是本质上更“高效”、“更快”或“更好”的。我通常使用 += 的原因是:1)它对我来说通常更清晰;2)这其实不重要 - user166390
感谢您的精准,nekno。您是正确的,将创建10个字符串对象!+1 - Ray Toal

7

很容易证明这篇文章并没有像人们所认为的那样改变了String对象:

string a = "a";
string b = a;
Console.WriteLine(object.ReferenceEquals(a, b)); // true
Console.WriteLine(a); // a
Console.WriteLine(b); // a
b += "b";
Console.WriteLine(object.ReferenceEquals(a, b)); // false
Console.WriteLine(a); // a
Console.WriteLine(b); // ab

这是因为x += y运算符相当于x = x + y,但输入的内容更少。
编程愉快。

6
使用反射器查看ILM代码,您将看到实际发生的情况。虽然您的代码逻辑上将新内容追加到字符串的末尾,但在幕后,编译器正在创建ILM代码,为每个赋值创建一个新字符串。
如果您在单个语句中连接字面字符串,情况会变得有些混乱:
str = "a" + "b" + "c" ...

在这种情况下,编译器通常足够聪明,不会创建所有额外的字符串(从而为垃圾回收工作),并将其翻译成等效于ILM代码的形式:
str = "abc"

那么,像这样分开写可能不会触发该优化。

4

Immutable意味着用于存储字符串变量的内存位置永远不会改变。

string str ="a";  //stored at some memory location
str+= "b";  // now str='ab' and stored at some other memory location
str+= "c";  // now str='abc' and stored at some other memory location

每当您更改字符串类型的值时,实际上您从未将新值存储在原始位置,而是不断将其存储在新的内存位置。and so on...
string a="Hello";
string b=a;

a="changed";

console.writeline(b); 

输出

你好,变量 b 仍然引用原始位置。

请查看 John Skeet 的页面。

http://csharpindepth.com/Articles/General/Strings.aspx


3

概念:

变量和实例是两个不同的概念。变量是保存值的东西。对于字符串来说,变量保存指向存储在其他地方的字符串的指针:即实例。

变量总是可以被分配和重新分配的,正如它名字所暗示的那样!=)

实例,就像我刚才说的,存在于其他地方,在字符串的情况下,它是不可更改的。

通过像您所做的那样连接字符串,实际上会创建许多不同的存储,每个字符串连接一个存储。

正确的方法:

要连接字符串,可以使用StringBuilder类:

StringBuilder b = new StringBuilder();
b.Append("abcd");
b.Append(" more text");
string result = b.ToString();

您也可以使用字符串列表,然后连接它们:
List<string> l = new List<string>();
l.Add("abcd");
l.Add(" more text");
string result = string.Join("", l);

2

@pst - 我同意可读性很重要,在大多数情况下,它在电脑上不会有影响,但如果您在系统资源受限的移动平台上呢?

重要的是要明白StringBuilder是连接字符串的最佳方式它更快、更高效。

您强调了一个重要的区别,即它是否明显 更快,并在哪些场景中。令人印象深刻的是,这种差异必须以低体积的滴答声来测量,因为无法以毫秒来测量。

重要的是要知道,在桌面平台的日常场景中,这种差异是不可感知的。但也很重要,对于构建大型字符串或执行数千个连接的边缘情况,或者进行性能优化,StringBuilder确实胜出。在非常大量的连接中,值得注意的是StringBuilder需要稍微更多的内存。

这绝不是一个完美的比较,但对于那些连接1,000,000个字符串的傻瓜来说,StringBuilder比普通的字符串连接要快约10分钟(在Win7 x64上使用Core 2 Duo E8500 @ 3.16GHz):

String concat   (10):           9 ticks,        0 ms,           8192 bytes
String Builder  (10):           2 ticks,        0 ms,           8192 bytes
String concat   (100):          30 ticks,       0 ms,           16384 bytes
String Builder  (100):          6 ticks,        0 ms,           8192 bytes
String concat   (1000):         1658 ticks,     0 ms,           1021964 bytes
String Builder  (1000):         29 ticks,       0 ms,           8192 bytes
String concat   (10000):        105451 ticks,   34 ms,          2730396 bytes
String Builder  (10000):        299 ticks,      0 ms,           40624 bytes
String concat   (100000):       15908144 ticks, 5157 ms,        200020 bytes
String Builder  (100000):       2776 ticks,     0 ms,           216888 bytes
String concat   (1000000):    1847164850 ticks, 598804 ms,      1999804 bytes
String Builder  (1000000):      27339 ticks,    8 ms,           2011576 bytes

代码:

class Program
    {
        static void Main(string[] args)
        {
            TestStringCat(10);
            TestStringBuilder(10);
            TestStringCat(100);
            TestStringBuilder(100);
            TestStringCat(1000);
            TestStringBuilder(1000);
            TestStringCat(10000);
            TestStringBuilder(10000);
            TestStringCat(100000);
            TestStringBuilder(100000);
            TestStringCat(1000000);
            TestStringBuilder(1000000);

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }

        static void TestStringCat(int iterations)
        {
            GC.Collect();
            String s = String.Empty;
            long memory = GC.GetTotalMemory(true);
            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < iterations; i++)
            {
                s += "a";
            }

            sw.Stop();
            memory = GC.GetTotalMemory(false) - memory;

            Console.WriteLine("String concat \t({0}):\t\t{1} ticks,\t{2} ms,\t\t{3} bytes", iterations, sw.ElapsedTicks, sw.ElapsedMilliseconds, memory);
        }

        static void TestStringBuilder(int iterations)
        {
            GC.Collect();
            StringBuilder sb = new StringBuilder();
            long memory = GC.GetTotalMemory(true);
            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < iterations; i++)
            {
                sb.Append("a");
            }

            sw.Stop();
            memory = GC.GetTotalMemory(false) - memory;

            Console.WriteLine("String Builder \t({0}):\t\t{1} ticks,\t{2} ms,\t\t{3} bytes", iterations, sw.ElapsedTicks, sw.ElapsedMilliseconds, memory);
        }
    }

1

实际上它并没有,它只是用新值覆盖了变量,每次都创建了一个新的字符串对象。


1

不变性意味着类不能被修改。对于每个 += 操作,你都在创建一个全新的字符串对象。


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