不可变只读引用类型和 FXCop 违规:不要声明只读可变引用类型

Posted

技术标签:

【中文标题】不可变只读引用类型和 FXCop 违规:不要声明只读可变引用类型【英文标题】:Immutable readonly reference types & FXCop Violation: Do not declare read only mutable reference types 【发布时间】:2010-02-16 16:25:38 【问题描述】:

我一直试图解决这个 FXCop 违规“DoNotDeclareReadOnlyMutableReferenceTypes”

MSDN:http://msdn.microsoft.com/en-us/library/ms182302%28VS.80%29.aspx

来自 MSDN 的可能导致此违规的代码:

namespace SecurityLibrary

    public class MutableReferenceTypes
    
        static protected readonly StringBuilder SomeStringBuilder;

        static MutableReferenceTypes()
        
            SomeStringBuilder = new StringBuilder();
        
    

从 Jon 的回答 here 和 here 中,我了解到保存对象引用的字段(在本例中为 SomeStringBuilder)是只读的,而不是对象本身(由 new StringBuilder() 创建)

以这个例子为例,一旦字段引用了对象,我将如何更改对象本身?我喜欢 Eric Lippert's example 如何更改只读数组,并希望看到任何其他可变引用类型的类似内容

【问题讨论】:

【参考方案1】:

readonly 表示您无法在构建后更改参考。

FXCop 的官方立场是,它建议只有无法修改的类型才应声明为readonly。因此像string 这样的东西是可以的,因为对象的值不能改变。但是StringBuilder 的值可以更改,但将其设置为只读只会阻止您在构造函数运行后将字段分配给不同的StringBuilder 实例或null

我不同意 FXCop 对这条规则的看法。只要人们理解这只是一种强制执行,即引用可能不会在构建后更改,那么就不会有混淆。

请注意,readonly 关键字使值类型不可变,但引用类型不是。

namespace SecurityLibrary

    public class MutableReferenceTypes
    
        static protected readonly StringBuilder SomeStringBuilder;

        static MutableReferenceTypes()
        
            // allowed
            SomeStringBuilder = new StringBuilder();
        

        void Foo()
        
            // not allowed
            SomeStringBuilder = new StringBuilder();
        

        void Bar()
        
            // allowed but FXCop doesn't like this
            SomeStringBuilder.AppendLine("Bar");
        
    

【讨论】:

+1 代码。在 MSDN 中链接您的代码。如果您早点回答,会标记您的答案。还是不错的!! 旧答案,但仍然非常相关。代码分析(就像现在一样)仍然有一些愚蠢的规则(例如这个),它们假设编码人员是白痴。任何对语言理解一半的人都知道readonly 是什么意思。设置新项目时我要做的第一件事是禁用大量 CA 规则,例如 CA2104。【参考方案2】:

由于问题中提供了 MutableReferenceTypes 类,因此由于 SomeStringBuilder 字段是私有的,因此您无法真正从任何外部调用者对其进行变异。

但是,类本身可能会改变字段。它目前没有,但它可以在以后的迭代中。

这是一个示例方法:

public static void Mutate()

    SomeStringBuilder.AppendLine("Foo");

调用 Mutate 方法将改变类,因为 SomeStringBuilder 现在将发生变化。

不变性不仅关乎代码的当前化身,还关乎保护自己免受未来错误的影响。并不是所有类都必须是不可变的,但如果您选择创建不可变类型,那么保持一致是最安全的。

【讨论】:

只是一个小错误:该字段是受保护的,而不是私有的,因此它从外部绝对可变。我猜 this 是 FXCop 反对的。 @Konrad Rudolph:好收获!我只是查看了第一个关键字并注意到它不是访问修饰符,因此必须默认为默认值(原文如此)。我没注意到关键字被调换了。【参考方案3】:

.Net 有一个此处允许的不可变引用类型列表,StringBuilder 不是其中之一。

抱怨是你正在构造的东西不是不可变的,虽然静态构造函数被调用一次并且类被初始化一次,这一切都保持不变,其余的都是可变的。一个线程可能会调用.Append(),然后是另一个……您会看到字符串构建器本身是如何变异的,而不是真正的readonly,因为它不断地改变状态/变异。

将其声明为readonly 确实是用词不当,因为那里引用的对象本身在不断变化。

【讨论】:

如何区分始终指向同一个对象的引用类型字段和在包含对象的生命周期内可能指向不同对象的引用类型字段?不了解引用类型是什么的人可能会感到困惑,但这样的人很容易对许多事情感到困惑,除非或直到他学会理解引用类型。【参考方案4】:

您无法更改引用,但对(可变)对象的任何调用都会更改其状态。

因此,由于SomeStringBuilder(在此示例中)本身是可变的,因此其内容可能会发生变化,这可能会误导该类的用户,因为它不是真正的“只读”。

基本上,readonly不保证对象不会改变,它只是说引用不会改变。

【讨论】:

引用不变这一事实可能对代码的其他部分至关重要(例如,某些其他对象可能会获取对 StringBuilder 的引用并期望能够获取其值);虽然人们当然应该知道被引用的对象可能会发生变异,但这并不意味着声明引用本身不可变没有任何用处。顺便说一句,即使代表也不总是一成不变的。由结构 mutator 形成的委托将对结构进行装箱,而装箱的结构将是可变的。【参考方案5】:

您不会更改对象的值。这就是规则的重点。任何声明为只读的字段实际上都应该是只读的。拥有一个只读的可变引用是矛盾的。如果您可以更改字段“指向”的值,那么它就不再是真正的只读了。将某个对象 A 的所有成员的值分配给由字段表示的某个对象 B 或简单地将 A 分配给该字段(当它们属于同一类型时)之间实际上没有功能上的区别,但是当字段中只有一个有效时被声明为只读,但由于您可以有效地更改该字段所代表的值,因此如前所述,它并不是真正的只读

【讨论】:

以上是关于不可变只读引用类型和 FXCop 违规:不要声明只读可变引用类型的主要内容,如果未能解决你的问题,请参考以下文章

泛型类型的 FXCop 违规

readonly

FXCop:“拥有一次性字段的类型应该是一次性的”

Python学习笔记 | 变量 + 引用 + 拷贝 + 作用域

为啥 FxCop 将 GC.KeepAlive() 标记为违规?

FXCop 违规 CA1716 IdentifiersShouldNotMatchKeyword