为什么这个结构是可变的可以吗?什么时候可变结构可以接受?

时间:2011-11-13 01:33:36

标签: c# .net performance struct immutability

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;
    }
}

5 个答案:

答案 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的所有类,你会发现一堆这些野兽: - )

  • System.Collections.Specialized.BitVector32(唯一公开的......)
  • System.Web.Util.SafeBitVector32(线程安全)
  • System.Web.Util.SimpleBitVector32
  • System.Runtime.Caching.SafeBitVector32(线程安全)
  • System.Configuration.SafeBitVector32(线程安全)
  • System.Configuration.SimpleBitVector32

如果您看起来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位向量的结构是合理的,但有一些注意事项:

  1. 我建议在对结构执行任何更新时使用Interlocked.CompareExchange循环,而不是直接使用普通的布尔运算符。如果一个线程尝试写入第3位而另一个线程尝试写入第8位,则除了稍微延迟之外,这两个操作都不应该干扰另一个操作。使用Interlocked.CompareExchange循环将避免错误行为的可能性(线程1读取值,线程2读取旧值,线程1写入新值,线程2写入基于旧值计算的值并撤消线程1的更改)而无需任何其他类型的锁定。
  2. 应避免使用修改“this”的属性设置者以外的结构成员。最好使用静态方法接受结构作为参考参数。调用修改“this”的结构成员通常与从语义和性能角度调用接受成员作为引用参数的静态方法相同,但是有一个关键区别:如果尝试传递只读结构通过引用静态方法,将得到编译器错误。相反,如果在只读结构上调用修改“this”的方法,则不会出现任何编译器错误,但不会发生预期的修改。由于即使可变结构在某些上下文中也可以被视为只读,因此在发生这种情况时获得编译器错误要比编译但不能工作的代码要好得多。
Eric Lippert喜欢谈论可变结构是多么邪恶,但重要的是要认识到他与他们的关系:他是C#团队中负责制作语言支持功能(如闭包和迭代器)的人之一。由于在创建.net的早期做出了一些设计决策,在某些情况下很难正确地支持值类型语义;如果没有任何可变值类型,他的工作会容易得多。我并不嫉妒Eric的观点,但重要的是要注意在框架和语言设计中可能很重要的一些原则并不适用于应用程序设计。

答案 4 :(得分:0)

如果我理解正确,你不能简单地使用SerializableAttribute制作一个可序列化的不可变结构。这是因为在反序列化期间,序列化程序实例化结构的默认实例,然后设置实例化后的所有字段。如果它们是只读的,则反序列化将失败。

因此,struct 具有是可变的,否则就需要一个复杂的序列化系统。