我只是好奇为什么结构体、字符串等是不可变的?为什么要把它们设为不可变,而其他对象则为可变。哪些方面被认为是使对象不可变的原因?
对于可变和不可变对象,内存的分配和释放方式是否有区别?
我只是好奇为什么结构体、字符串等是不可变的?为什么要把它们设为不可变,而其他对象则为可变。哪些方面被认为是使对象不可变的原因?
对于可变和不可变对象,内存的分配和释放方式是否有区别?
void M()
{
S s = whatever;
... lots of code ...
s.Mutate();
... lots more code ...
Console.WriteLine(s.Foo);
...
}
现在你想将其中一些代码重构为一个帮助方法:
void Helper(S s)
{
... lots of code ...
s.Mutate();
... lots more code ...
}
错误!应该是 (ref S s) -- 如果你不这样做,那么突变将发生在 s 的一个副本上。如果一开始就不允许突变,那么所有这些问题都会消失。
记得我第一个关于不可变结构保持事实的观点吗?
假设字符串是可变的:
public static File OpenFile(string filename)
{
if (!HasPermission(filename)) throw new SecurityException();
return InternalOpenFile(filename);
}
如果有恶意的调用者在安全检查之后、文件打开之前更改了文件名,会怎么样呢?代码刚刚打开了一个他们可能没有权限访问的文件!
再次强调,可变数据很难推理。您希望“该调用者被授权查看此字符串描述的文件”是永恒的真相,而不是“直到发生突变”。对于可变字符串,为了编写安全代码,我们需要不断地复制我们知道不会改变的数据。
哪些因素被认为可以使对象不可变?
类型是否逻辑上代表一个“永恒”的值?数字12就是数字12;它不会改变。整数应该是不可变的。点(10, 30)就是点(10, 30);它不会改变。点应该是不可变的。字符串“abc”就是字符串“abc”;它不会改变。字符串应该是不可变的。列表(10, 20, 30)不会改变等等。
有时类型表示会变化的事物。玛丽·史密斯的姓氏是史密斯,但明天她可能成为玛丽·琼斯。或者今天的史密斯小姐可能明天成为史密斯博士。外星人现在有五十个生命值,但被激光束击中后只剩下十个。某些事物最好表示为突变。
可变和不可变对象的内存分配和释放方式有什么区别吗?
并没有这样的区别。但正如我之前提到的,不可变值的一个好处是,您可以重复使用它们的部分而无需进行复制。因此,在这种意义上,内存分配可能会非常不同。
结构体并不一定是不可变的,但是可变的结构体是有害的。
创建可变的结构体可能会导致应用程序出现各种奇怪的行为,因此,它们被认为是一个非常糟糕的想法(这源于它们看起来像引用类型,但实际上是值类型,并且每次传递时都会被复制)。
另一方面,字符串是不可变的。这使得它们天生是线程安全的,并且可以通过字符串内部化进行优化。如果需要动态构建复杂的字符串,则可以使用StringBuilder
。
IEnumerator
实现是结构体。例如http://msdn.microsoft.com/en-us/library/x854yt9s.aspx不要通过属性公开其中之一,否则会遭受可怕的后果。 :) - JorenFoo
具有类型为List<Integer>
的字段Bar
,它持有对包含(1,2,3)的列表的引用,则具有对该同一列表的引用的其他代码可以修改它,使得Bar
持有对包含(4,5,6)的列表的引用,即使那个其他代码根本没有访问Bar
的权限。相比之下,如果Foo
具有类型为System.Drawing.Point
的字段Biz
,任何修改Biz
的方面的方式都必须具有对该字段的写入访问权限。看着实际的 Microsoft 属性,有没有办法判断对 myArray[4]
的写入是否会影响 myMatrix
?即使查看页面 http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.matrix.elements.aspx 也无法确定吗?如果使用基于结构体的等效属性编写该属性,则不会存在任何混淆;返回结构体的属性将仅返回六个数字的当前值。更改 myTransform.dx
将只是对未连接到任何其他内容的浮点变量进行写入。任何不喜欢更改 myTransform.dx
不会影响 myMatrix
的人应该同样感到烦恼,因为除了明显的 myMatrix
和 myTransform
的独立性外,写入 myArray[4]
也不会影响 myMatrix
,而 myMatrix
和 myArray
的独立性则不是这样。
结构体类型并不是不可变的,但字符串是。使自己的类型不可变很容易,只需不提供默认构造函数,将所有字段设为私有,并且不定义任何更改字段值的方法或属性。如果有一个应该改变对象的方法,则返回一个新对象。这样做还有一个内存管理的角度,因为你往往会创建很多副本和垃圾。
结构体可以是可变的,但这是一个不好的想法,因为它们具有复制语义。如果您对结构体进行更改,则实际上可能正在修改副本。跟踪已更改的内容非常棘手。
可变结构体会导致错误。
IComparer<T>
)。 - Dan Bryant