.net 中的结构通常应该符合两种风格中的一种,我称之为“透明”和“不透明”。请注意,MSDN 指南似乎是由认为一切都表现得像类对象的人编写的,而不能像类对象那样表现是一种缺陷。因此,他们忽略了这样一个事实,即透明结构本身可以具有有用的语义,并且在许多情况下,使不透明结构效率低下的因素可能会使类更加低效。
透明结构是所有字段都公开的结构,结构的状态无非就是其字段中值的组合。透明结构可能具有基于其字段值计算的只读“便利属性”(例如Vector3d
,具有字段 DX、DY 和 DZ 的透明结构可能具有Length
基于这些字段计算的属性),但它应该很明显,这些属性并不构成结构状态的一部分,而是在字段上执行计算的便捷简写。例如,vec.Length
将作为SomeLibrary.Distance3d(vec.DX, vec.DY, vec.DZ)
.
透明结构类型的变量为每个字段分配一个变量;如果需要,可以将每个字段作为其自己的单独变量进行访问。按值传递透明结构类型的成本与单独传递其所有字段的成本大致相同(在某些情况下,它可能更有效;在其他情况下,则更少)。传递透明结构类型ref
与传递对象引用具有相同的固定成本,无论字段数如何。
如果当前版本满足以下所有标准并且所有可能的未来版本也将满足以下标准,则结构在许多情况下应该是透明的[意味着不符合标准的结构将不被视为兼容的替代品]
- 有一些固定的可读成员集(字段或属性)暴露了它的整个状态
- 给定这些成员的任何一组所需值,可以使用这些值创建一个实例(禁止值组合)。
- 结构的默认值应该是将所有这些成员设置为其各自类型的默认值。
如果一个结构符合上述标准,公开其字段将不允许外部代码做任何它不能做的事情,除非它可能允许快速执行否则会很慢的操作,并且可能允许线程安全操作否则需要使用原子原语安全地执行锁定。
当然,并非所有结构都符合上述标准,但如果将那些确实符合标准的结构透明化,则通常可以提高性能——有时甚至是相当大的。事实上,结构通常被认为只比类快一点的大部分原因是它们的性能因不必要地使其不透明而受到阻碍。
不透明结构是其状态在语义上表示其字段中的值以外的东西。例如,Decimal
大小为 1234 且指数为 2 的 a 可能表示数值 12.34。不可能只改变不透明结构的一个方面,除非为整个事物计算一个新值。不透明结构可能会尝试对其字段强制执行不变量,但重要的是要注意,由于结构分配的工作方式,在许多情况下滥用线程(但没有任何特殊执行权限)的代码可能会被滥用线程以生成违反结构不变量的结构实例。
Microsoft 的指南在应用于不透明结构时非常好。此类结构通常应避免暴露属性设置器。类型喜欢Point
并且Rectangle
不代表背离该规则。 相反,它们代表的类型本来就不应该是不透明的结构——它们应该是透明的结构。在某些情况下,公开属性设置器的不透明结构可以提供无法通过任何其他方式实现的语义,但这种结构有一些令人讨厌的陷阱,通常使它们不如实现相同目的的其他方式可取。
关于您提出的结构,我建议将其设置为具有三个字段的透明结构,并且具有MedianPrice
每次调用时计算这三个字段的中位数的属性,或者具有四个字段的不透明结构,它将计算MedianPrice 在其构造函数中。我的决定将基于阅读的相对频率MedianPrice
,与代码想要修改其中一个价格而不修改其他价格的相对频率相比。如果前者更常见,则为不透明结构。如果是后者,透明结构。请注意,如果一开始使用透明结构并编写代码以利用它,则可能难以调整代码以处理不透明结构。相比之下,为使用不透明结构而编写的代码也可以使用透明结构,但不会像其他情况下那样高效。
请注意,透明结构和其他类型之间的效率差异随着结构大小的增加而增长。例如,假设需要存储许多 3d 三角形的坐标。可以定义一个Triangle3d
包含三个类型字段Point3d
(每个字段包含三个数字 X、Y、Z)的透明结构,并将所有三角形存储在Triangle3d[]
. 给定这样的定义,人们可以轻松地修改任何三角形中单个点的单个坐标,而无需触摸甚至读取任何其他坐标。相比之下,如果使用不透明结构或不可变类类型,即使更改单个属性也需要将所有信息从旧实例复制到新实例。类型的字段越多,使用不透明或不可变类型的成本就越高。如果使用可变类类型,则修改会很便宜,但是没有一种很好的方法可以保存上述数组以使调用者可以在不暴露底层存储对象的情况下使用三角形的坐标,而不是手动操作-将所有字段值复制到新的不可变对象。 因此,与 Microsoft 的指导方针相反,对于许多使用模式,类型具有的字段越多,其作为透明结构而不是类的优势就越大。
顺便说一句,有些人会争辩说暴露字段结构违反了封装。我会反驳。likeKeyValuePair<TKey,TValue>
应该包含一个 type 字段TKey
和另一个 type字段TValue
。由于可以自由读取这两个字段,因此可以为它们分配对其各自类型有效的任何值组合,并且没有任何有用的类型的未来版本可能会做透明结构不会做的事情能够做得一样好,所谓的“封装”除了在添加零值的同时降低代码效率之外什么也没做。