Eric Lippert told me I should "try to always make value types immutable",所以我想我应该尝试始终使值类型不可变。
但是,我刚刚在System.Web.Util.SimpleBitVector32
程序集中找到了这个内部可变结构System.Web
,这让我觉得必须有一个很好的理由来拥有一个可变结构。我猜他们这样做的原因是因为它在测试中表现得更好,而且他们把它保持在内部以阻止它的误用。然而,这是猜测。
我已经C& P'd这个结构的来源。什么是设计决定使用可变结构的合理性?一般来说,这种方法可以获得什么样的好处,什么时候这些好处足以证明潜在的损害?
[Serializable, StructLayout(LayoutKind.Sequential)]
internal struct SimpleBitVector32
{
private int data;
internal SimpleBitVector32(int data)
{
this.data = data;
}
internal int IntegerValue
{
get { return this.data; }
set { this.data = value; }
}
internal bool this[int bit]
{
get {
return ((this.data & bit) == bit);
}
set {
int data = this.data;
if (value) this.data = data | bit;
else this.data = data & ~bit;
}
}
internal int this[int mask, int offset]
{
get { return ((this.data & mask) >> offset); }
set { this.data = (this.data & ~mask) | (value << offset); }
}
internal void Set(int bit)
{
this.data |= bit;
}
internal void Clear(int bit)
{
this.data &= ~bit;
}
}
答案 0 :(得分:18)
鉴于有效负载是一个32位整数,我会说这很容易被写成一个不可变的结构,可能对性能没有影响。无论您是调用更改32位字段值的mutator方法,还是使用新的32位结构替换32位结构,您仍然可以执行完全相同的内存操作。
可能有人想要的东西有点像数组(虽然实际上只是32位整数中的位),所以他们决定使用索引器语法,而不是不那么明显.WithTheseBitsChanged()返回新结构的方法。由于它不会被MS网络团队以外的任何人直接使用,甚至可能不是很多人甚至在网络团队中使用,我认为他们在设计决策方面比构建公共API的人有更多的余地。
所以,不,可能不是那种表现方式 - 这可能只是程序员在编码风格方面的个人偏好,并且从来没有任何令人信服的理由来改变它。
如果您正在寻找设计指南,我不会花太多时间查看尚未被公开消费的代码。
答案 1 :(得分:15)
SimpleBitVector32
是可变的,原因与BitVector32
可变的原因相同。在我看来,不可变指南只是一个指南;但是,这样做应该有一个非常好的理由。
同样考虑Dictionary<TKey, TValue>
- 我进入了一些extended details here。字典的Entry
结构是可变的 - 您可以随时更改TValue。但是,Entry
逻辑上代表值。
可变性必须有意义。我同意@JoeWhite:有人想要的东西就像一个数组(虽然真的只是一个32位整数的位);另外,两个BitVector结构都可以很容易地......不可变。
但是,作为一个全面的陈述,我不同意它可能只是程序员在编码风格中的个人偏好而更倾向于从来没有 [也没有] 任何令人信服的理由来改变它。只需了解并理解使用可变结构的责任。
修改强>
为了记录,我衷心同意 总是尝试以使结构不可变。如果您发现需求决定了成员的可变性,请重新审视设计决策并让同事参与进来。
<强>更新强>
在考虑可变值类型v。不可变时,我最初对我的性能评估没有信心。但是,正如@David指出的那样,Eric Lippert writes this:
有些时候你需要拧掉最后一点性能 超出系统。在这些情况下,你有时必须做一个 代码之间的权衡,干净,纯粹,健壮, 可理解的,可预测的,可修改的和没有的代码 以上,但速度非常快。
我加粗纯因为可变结构不符合结构应该是不可变的的纯粹理想。写一个可变结构有副作用:可理解性和可预测性受到损害,正如Eric继续解释:
可变值类型......表现 以许多人发现深刻违反直觉的方式,从而 使编写错误的代码(或容易正确的代码)变得容易 意外地变成了错误的代码。)但是,是的,它们真的很快。
Eric的观点是,您,因为设计师和/或开发人员需要做出有意识且明智的决定。你怎么知道的?埃里克也解释说:
我会考虑编写两个基准解决方案 - 一个使用 可变结构,一个使用不可变结构 - 并运行一些 现实以用户方案为重点的基准测试。但事情就是这样:不要选择更快的一个。相反,在运行基准之前决定 慢得令人无法接受。
我们知道更改值类型 比创建新值类型更快;但考虑正确性:
如果两种解决方案都可以接受,请选择干净的解决方案, 正确而且足够快。
关键是快足够 来抵消选择mutable而不是不可变的副作用。只有你可以确定。
答案 2 :(得分:14)
实际上,如果你在.NET框架中搜索包含BitVector的所有类,你会发现一堆这些野兽: - )
如果您看起来here驻留在System.Configuration.SimpleBitVector32的SSCLI(Microsoft Shared Source CLI,又名ROTOR)源中,您会发现此评论:
//
// This is a cut down copy of System.Collections.Specialized.BitVector32. The
// reason this is here is because it is used rather intensively by Control and
// WebControl. As a result, being able to inline this operations results in a
// measurable performance gain, at the expense of some maintainability.
//
[Serializable()]
internal struct SimpleBitVector32
我相信这说明了一切。我认为System.Web.Util更复杂,但建立在相同的基础上。
答案 3 :(得分:2)
使用如此处所示的32位或64位向量的结构是合理的,但有一些注意事项:
答案 4 :(得分:0)
如果我理解正确,你不能简单地使用SerializableAttribute
制作一个可序列化的不可变结构。这是因为在反序列化期间,序列化程序实例化结构的默认实例,然后设置实例化后的所有字段。如果它们是只读的,则反序列化将失败。
因此,struct 具有是可变的,否则就需要一个复杂的序列化系统。