具有公共可变字段或属性的结构不是邪恶的。
突变“ this”的结构方法(不同于属性设置器)有些邪恶,仅是因为.net没有提供将它们与不区分此方法的方法。不会使“ this”突变的结构方法即使在只读结构上也应可调用,而无需进行防御性复制。变异“ this”的方法在只读结构上完全不可调用。由于.net不想禁止在只读结构上调用不会修改“ this”的结构方法,但又不想允许对只读结构进行突变,因此它会防御性地以只读方式复制结构。仅上下文,可以说是两全其美。
尽管在只读上下文中处理自变异方法存在问题,但是可变结构通常提供的语义要远远优于可变类类型。请考虑以下三个方法签名:
struct PointyStruct {public int x,y,z;};
class PointyClass {public int x,y,z;};
void Method1(PointyStruct foo);
无效的Method2(ref PointyStruct foo);
void Method3(PointyClass foo);
对于每种方法,请回答以下问题:
- 假设该方法不使用任何“不安全”代码,是否可以修改foo?
- 如果在调用该方法之前不存在对“ foo”的外部引用,那么此后是否可以存在外部引用?
答案:
问题1:
Method1()
否(意图明确)
Method2()
:是(明确意图)
Method3()
:是(不确定意图)
问题2
Method1()
:否
Method2()
:否:否(除非不安全)
Method3()
:是
Method1无法修改foo,也永远不会获得引用。Method2获得了对foo的短暂引用,它可以使用它以任意顺序多次修改foo的字段,直到返回为止,但是它不能持久保存该引用。在Method2返回之前,除非它使用了不安全的代码,否则可能已经完全删除了其“ foo”引用所创建的所有副本。与Method2不同,Method3获得了对foo的混杂共享引用,并且没有告诉它可能对它做什么。它可能根本不会更改foo,它可能会更改foo然后返回,或者它可能将foo的引用提供给另一个线程,该线程可能会在任意将来的某个时间以某种方式对其进行更改。
结构数组提供了奇妙的语义。给定Rectangle类型的RectArray [500],很明显地知道如何将元素123复制到元素456,然后在一段时间后将元素123的宽度设置为555,而不会干扰元素456。“ RectArray [432] = RectArray [321] ]; ...; RectArray [123] .Width = 555;“。知道Rectangle是具有称为Width的整数字段的结构,将告诉所有人都需要了解上述语句。
现在假设RectClass是一个与Rectangle具有相同字段的类,并且想要对RectClass类型的RectClassArray [500]执行相同的操作。也许该数组应该包含对可变的RectClass对象的500个预初始化的不可变引用。在这种情况下,正确的代码应类似于“ RectClassArray [321] .SetBounds(RectClassArray [456]); ...; RectClassArray [321] .X = 555;”。也许假设该数组包含不会更改的实例,所以正确的代码将更像是“ RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = New RectClass(RectClassArray [321] ]); RectClassArray [321] .X = 555;“ 要知道应该做什么,就必须对RectClass有更多了解(例如,它是否支持复制构造函数,copy-from方法等)。)和数组的预期用途。没有比使用结构干净的地方了。
可以肯定的是,不幸的是,除了数组之外,任何容器类都没有一种好的方法来提供struct数组的清晰语义。如果一个人希望用一个字符串为一个集合建立索引,那么最好的办法就是提供一个通用的“ ActOnItem”方法,该方法将接受索引的字符串,一个通用参数以及一个将被传递的委托。通过引用通用参数和收集项。这将允许几乎与struct数组相同的语义,但是除非能够使vb.net和C#人员提供良好的语法,否则即使性能合理,代码也将显得笨拙(通过通用参数会允许使用静态委托,并且无需创建任何临时类实例)。
就个人而言,我很讨厌仇恨的埃里克·利珀特(Eric Lippert)等人。关于可变值类型。与在各处使用的混杂引用类型相比,它们提供了更简洁的语义。尽管.net支持值类型存在一些限制,但在许多情况下,可变值类型比任何其他类型的实体更适合。
int
s,bool
s和所有其他值类型都是邪恶的一样。有可变性和不变性的情况。这些情况取决于数据扮演的角色,而不是取决于内存分配/共享的类型。