.NET 字符串真的应该被认为是不可变的吗?
Should .NET strings really be considered immutable?
考虑以下代码:
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('[=13=]', inputData.Length);
fixed (char* ptr = result)
{
// fill the result with input data
}
return result; // return it
}
如果您在速度关键领域(例如编码)工作,这可能会节省大量内存分配/性能成本。
我想您可能会说这不算数,因为它 "uses a hack" 使指针可变,但话又说回来,首先支持将字符串分配给指针的是 C# 语言设计者。 (事实上,这是在 String
和 StringBuilder
内部完成的 all the time,所以从技术上讲,您可以用它来创建自己的 StringBuilder。)
那么,.NET 字符串真的应该被认为是不可变的吗?
C#语言规范的§18.6(fixed
语句)专门解决了通过固定指针修改字符串的情况,并指出这样做可以导致未定义的行为:
Modifying objects of managed type through fixed pointers can results in undefined behavior. For example, because strings are immutable, it is the programmer’s responsibility to ensure that the characters referenced by a pointer to a fixed string are not modified.
我只需要玩这个并进行实验以确认 字符串文字 的地址是否指向相同的内存位置。
结果是:
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()
的进一步测试表明它们对于 foo
和 foo2
确实是相等的,而对于 foo
和 fooCopy
.
不同的引用
看看 字符串文字 如何在内存中操作并影响仅引用它的其他变量是很有趣的。由于存在这种行为,我们应该注意的事情之一。
考虑以下代码:
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('[=13=]', inputData.Length);
fixed (char* ptr = result)
{
// fill the result with input data
}
return result; // return it
}
如果您在速度关键领域(例如编码)工作,这可能会节省大量内存分配/性能成本。
我想您可能会说这不算数,因为它 "uses a hack" 使指针可变,但话又说回来,首先支持将字符串分配给指针的是 C# 语言设计者。 (事实上,这是在 String
和 StringBuilder
内部完成的 all the time,所以从技术上讲,您可以用它来创建自己的 StringBuilder。)
那么,.NET 字符串真的应该被认为是不可变的吗?
§18.6(fixed
语句)专门解决了通过固定指针修改字符串的情况,并指出这样做可以导致未定义的行为:
Modifying objects of managed type through fixed pointers can results in undefined behavior. For example, because strings are immutable, it is the programmer’s responsibility to ensure that the characters referenced by a pointer to a fixed string are not modified.
我只需要玩这个并进行实验以确认 字符串文字 的地址是否指向相同的内存位置。
结果是:
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()
的进一步测试表明它们对于 foo
和 foo2
确实是相等的,而对于 foo
和 fooCopy
.
看看 字符串文字 如何在内存中操作并影响仅引用它的其他变量是很有趣的。由于存在这种行为,我们应该注意的事情之一。