.NET字符串真的应该被认为是不可变的吗?

时间:2015-08-10 23:17:55

标签: c# .net string immutability

请考虑以下代码:

unsafe
{
    string foo = string.Copy("This can't change");

    fixed (char* ptr = foo)
    {
        char* pFoo = ptr;
        pFoo[8] = pFoo[9] = ' ';
    }

    Console.WriteLine(foo); // "This can   change"
}

这会创建指向foo的第一个字符的指针,将其重新指定为可变,并将字符8和9的位置更改为' '

注意我从未真正重新分配foo;相反,我通过修改其状态来改变其值,或者改变字符串。因此,.NET字符串是可变的。

这样做效果很好,事实上,以下代码:

unsafe
{
    string bar = "Watch this";

    fixed (char* p = bar)
    {
        char* pBar = p;
        pBar[0] = 'C';
    }

    string baz = "Watch this";
    Console.WriteLine(baz); // Unrelated, right?
}
由于字符串文字实习,

将打印"Catch this"

这有很多适用的用途,例如:

string GetForInputData(byte[] inputData)
{
    // allocate a mutable buffer...
    char[] buffer = new char[inputData.Length];

    // fill the buffer with input data

    // ...and a string to return
    return new string(buffer);
}

被替换为:

string GetForInputData(byte[] inputData)
{
    // allocate a string to return
    string result = new string('\0', inputData.Length);

    fixed (char* ptr = result)
    {
        // fill the result with input data
    }

    return result; // return it
}

如果您在速度关键字段(例如编码)中工作,这可以节省潜在的巨大内存分配/性能成本。

我想你可以说这不算数,因为它"使用hack"使指针变为可变,但是再一次是C#语言设计者支持首先将字符串赋值给指针。 (事实上​​,这是在StringStringBuilder内部all the time完成的,因此从技术上讲,您可以使用此方法创建自己的StringBuilder。)

那么,.NET字符串真的应该被认为是不可变的吗?

2 个答案:

答案 0 :(得分:6)

§18.6的C#语言规范( fixed语句)专门解决了通过固定指针修改字符串的情况,并指出这样做会导致未定义的行为:

  

通过固定指针修改托管类型的对象可能导致未定义的行为。例如,因为字符串是不可变的,所以程序员有责任确保不修改指向固定字符串的指针所引用的字符。

答案 1 :(得分:1)

我只需要玩这个并尝试确认 string literal 的地址是否指向同一个内存位置。

结果是:

string foo = "Fix value?"; //New address: 0x02b215f8
string foo2 = "Fix value?"; //Points to same address: 0x02b215f8
string fooCopy = string.Copy(foo); //New address: 0x021b2888

fixed (char* p = foo)
{
    p[9] = '!';
}

Console.WriteLine(foo);
Console.WriteLine(foo2);
Console.WriteLine(fooCopy);

//Reference is equal, which means refering to same memory address
Console.WriteLine(string.ReferenceEquals(foo, foo2)); //true

//Reference is not equal, which creates another string in new memory address
Console.WriteLine(string.ReferenceEquals(foo, fooCopy)); //false

我们看到foo初始化一个字符串文字,指向我PC中的0x02b215f8内存地址。为foo2分配相同的字符串文字引用相同的内存地址。并且创建相同字符串文字的副本会产生一个新副本。通过string.ReferenceEquals()进行的进一步测试表明,foofoo2确实相同,foofooCopy的参考不同。

有趣的是,字符串文字如何在内存中进行操作,并影响其他仅引用它的变量。由于存在这种行为,我们应该注意的一件事。