Nir*_*ngh 182 .net c# string immutability
众所周知,String是不可变的.String不可变的原因是什么,StringBuilder类的引入是可变的?
Jon*_*nna 236
out或ref(因为它改变了引用,而不是对象).因此,程序员知道如果string x = "abc"在方法的开头,并且在方法的主体中没有改变,那么x == "abc"在方法的最后."abc" == "ab" + "c".虽然这不需要不变性,但是对这样一个字符串的引用在其整个生命周期中总是等于"abc"(这确实需要不变性)的事实使得使用作为保持与先前值相等的关键是至关重要的,更容易确保正确性(字符串确实常用作键).Christmas.AddMonths(1)产生新的DateTime而不是改变可变的.(另一个例子,如果我作为一个可变对象改变我的名字,改变的是我正在使用的名字,"Jon"仍然是不可变的,其他Jons将不受影响.return this.由于副本无论如何都无法改变,假装某些东西是它自己的副本是安全的.总之,对于没有经历变化的对象作为其目的的一部分,存在不可变的许多优点.主要的缺点是需要额外的构造,尽管在这里它经常被夸大(记住,在StringBuilder变得比等效的连接系列更有效之前,你必须做几个附加,具有它们固有的结构).
如果可变性是对象的目的的一部分(谁想要通过其工资永远不会改变的Employee对象建模),那将是一个缺点,尽管有时甚至它可能是有用的(在许多web和其他无状态中)应用程序,执行读取操作的代码与执行更新的代码是分开的,使用不同的对象可能是很自然的 - 我不会使对象成为不可变的然后强制该模式,但如果我已经拥有该模式,我可能会使我的"读取"对象性能和正确性 - 保证收益是不可变的).
写时复制是一个中间立场.这里的"真实"类包含对"状态"类的引用.状态类在复制操作上共享,但如果更改状态,则会创建状态类的新副本.这通常与C++一起使用而不是C#,这就是为什么它的std:string享有不可变类型的一些但不是全部优点,同时保持可变性.
Ree*_*sey 74
使字符串不可变具有许多优点.它提供自动线程安全性,并使字符串以简单有效的方式表现为内在类型.它还允许在运行时提高效率(例如允许有效的字符串实习以减少资源使用),并且具有巨大的安全优势,因为第三方API调用不可能更改字符串.
添加StringBuilder是为了解决不可变字符串的一个主要缺点 - 不可变类型的运行时构造会导致很多GC压力并且本质上很慢.通过创建一个显式的可变类来处理这个问题,解决了这个问题,而没有在字符串类中添加不必要的复杂性.
Car*_*ñoz 21
字符串并不是真正不可改变的.它们只是公开不变的.这意味着您无法从其公共界面修改它们.但在内部实际上是可变的.
如果你不相信我String.Concat用反射器来看待定义.最后一行是......
int length = str0.Length;
string dest = FastAllocateString(length + str1.Length);
FillStringChecked(dest, 0, str0);
FillStringChecked(dest, length, str1);
return dest;
Run Code Online (Sandbox Code Playgroud)
正如您所看到的那样,FastAllocateString返回一个空的但已分配的字符串,然后将其修改为FillStringChecked
实际上这FastAllocateString是一个extern方法,并且FillStringChecked是不安全的,所以它使用指针来复制字节.
也许有更好的例子,但这是我到目前为止找到的.
kol*_*osy 14
字符串管理是一个昂贵的过程.保持字符串不可变允许重复使用重复的字符串,而不是重新创建.
Neb*_*oft 13
String是一种引用类型,因此它永远不会被复制,而是通过引用传递.将其与C++ std :: string对象(不是不可变的)进行比较,该对象由value传递.这意味着如果你想在Hashtable中使用String作为键,你在C++中就可以了,因为C++会复制字符串以将键存储在哈希表中(实际上是std :: hash_map,但仍然存在)以供以后比较.所以即使你以后修改了std :: string实例,你也没关系.但在.Net中,当您在Hashtable中使用String时,它将存储对该实例的引用.现在假设字符串不是不可变的,看看会发生什么:1.有人将一个带有键"hello"的值x插入到Hashtable中.2. Hashtable计算String的哈希值,并将对字符串的引用和值x放在适当的存储桶中.3.用户将String实例修改为"bye".4.现在有人想要哈希表中与"hello"关联的值.它最终会查找正确的存储桶,但在比较字符串时会显示"bye"!="hello",因此不返回任何值.也许有人想要"再见"的价值?"bye"可能有不同的哈希值,因此哈希表会在不同的桶中查找.该存储桶中没有"再见"键,因此仍未找到我们的输入.
使字符串不可变意味着步骤3是不可能的.如果有人修改了字符串,他正在创建一个新的字符串对象,只留下旧的字符串对象.这意味着哈希表中的键仍然是"你好",因此仍然是正确的.
因此,可能除其他外,不可变字符串是一种方法,可以使通过引用传递的字符串用作哈希表或类似字典对象中的键.
只是把它扔进去,一个经常被遗忘的视图是安全的,如果字符串是可变的,请记录这个场景:
string dir = "C:\SomePlainFolder";
//Kick off another thread
GetDirectoryContents(dir);
void GetDirectoryContents(string directory)
{
if(HasAccess(directory) {
//Here the other thread changed the string to "C:\AllYourPasswords\"
return Contents(directory);
}
return null;
}
Run Code Online (Sandbox Code Playgroud)
如果你被允许在传递完成后改变它们,你会看到它是如何非常非常糟糕的.
字符串作为.NET中的引用类型传递。
引用类型在堆栈上放置一个指向托管堆上实际实例的指针。这与值类型不同,后者将整个实例保存在堆栈中。
当将值类型作为参数传递时,运行时将在堆栈上创建该值的副本,并将该值传递给方法。这就是为什么必须使用'ref'关键字传递整数以返回更新的值的原因。
传递引用类型后,运行时将在堆栈上创建指针的副本。那个复制的指针仍然指向引用类型的原始实例。
字符串类型具有重载的=运算符,该运算符将创建自身的副本,而不是指针的副本-使其行为更像值类型。但是,如果仅复制了指针,则第二个字符串操作可能会意外覆盖另一个类的私有成员的值,从而导致一些令人讨厌的结果。
正如其他文章所提到的,StringBuilder类允许创建字符串而没有GC开销。
| 归档时间: |
|
| 查看次数: |
97420 次 |
| 最近记录: |