首页 文章

为什么Microsoft建议使用具有可变值的只读字段?

提问于
浏览
36

微软在Design Guidelines for Developing Class Libraries中说:

不要将可变类型的实例分配给只读字段 . 使用可变类型创建的对象可以在创建后进行修改 . 例如,数组和大多数集合是可变类型,而Int32,Uri和String是不可变类型 . 对于包含可变引用类型的字段,只读修饰符可防止覆盖字段值,但不保护可变类型不被修改 .

这简单地重述了readonly的行为,但没有解释为什么使用readonly是不好的 . 其含义似乎是许多人不理解“只读”的含义,并错误地认为只读字段是不可改变的 . 实际上,它建议使用“readonly”作为代码文档,指示深层不变性 - 尽管编译器无法强制执行此操作 - 并且禁止将其用于其正常功能:确保字段的值在之后不会发生变化该对象已经构建 .

我对使用"readonly"来表示编译器理解的正常含义之外的其他内容感到不安 . 我觉得它鼓励人们误解"readonly"的含义,并且进一步期望它意味着代码作者可能不想要的东西 . 我觉得它排除了在可能有用的地方使用它 - 例如表明两个可变对象之间的某些关系在其中一个对象的生命周期内保持不变 . 假设读者不理解"readonly"的含义的概念似乎与微软的其他建议相矛盾,例如FxCop的"Do not initialize unnecessarily"规则,该规则假定您的代码的读者是该语言的专家并应该知道(例如)bool字段自动初始化为false,并阻止您提供显示"yes, this has been consciously set to false; I didn't just forget to initialize it"的冗余 .

那么,首先,为什么Microsoft建议不要使用readonly来引用可变类型?我也有兴趣知道:

  • 您是否在所有代码中都遵循此设计指南?

  • 当你在一段你没写过的代码中看到"readonly"时,你有什么期望?

6 回答

  • 21

    微软有一些这样的特殊建议 . 另一个立即想到的不是在公共成员中嵌套泛型类型,如 List<List<int>> . 我尽量避免使用这些结构,但在我认为使用合理时忽略了新手友好的建议 .

    对于只读字段 - 我试图避免使用公共字段,而是去寻找属性 . 我认为对此也提出了一些建议,但更重要的是,当某个字段在某个属性时不起作用时(通常与数据绑定和/或可视化设计器有关),有时会出现这种情况 . 通过创建所有公共字段属性,我避免任何潜在的问题 .

  • 1

    不要将可变类型的实例分配给只读字段 .

    我快速浏览了“框架设计指南”一书(第161-162页),它基本上说明了你对Joe Duffy发表的另一条评论的解释指南's raison-d'être:

    本指南试图保护您的目的是相信您已经暴露了一个深度不可变的对象图,而实际上它很浅,然后编写假定整个图是不可变的代码 . - 乔达菲

    I personally think that the keyword readonly was named badly. 事实上,它只指定引用的常量,而不是引用对象的常量,这很容易产生误导性的情况 .

    我认为如果 readonly 使引用的对象也是不可变的,而不仅仅是引用,那将是更可取的,因为这是关键字所暗示的 .

    为了纠正这种不幸的情况,制定了指南 . 虽然我认为它的建议从人的角度来看是合理的(它没有查找它们的定义,而且这个词暗示了深刻的不变性),我有时希望,当谈到声明const-ness时,C#会提供一个类似于C提供的自由,您可以在指针上或指向对象上定义 const ,或者在两者上或在任何内容上定义 const .

  • 1

    您正在寻找的语法受C / CLI语言支持:

    const Example^ const obj;
    

    第一个const使引用的对象不可变,第二个使引用成为不可变的 . 后者等同于C#中的readonly关键字 . 试图逃避它会产生编译错误:

    Test^ t = gcnew Test();
    t->obj = gcnew Example();   // Error C3892
    t->obj->field = 42;         // Error C3892
    Example^ another = t->obj;  // Error C2440
    another->field = 42;
    

    然而,它是烟雾和镜子 . 不可变性由编译器验证,而不是由CLR验证 . 另一种托管语言可以修改它们 . 哪个是问题的根源,CLR只是不支持它 .

  • 7

    如果一个字段是只读的,你会发现无法改变它或与它有关的任何东西 . 如果我知道Bar是Foo的唯一领域,我显然不会说

    Foo foo = new Foo();
    foo.Bar = new Baz();
    

    但我可以逃脱话

    foo.Bar.Name = "Blah";
    

    如果对象支持Bar实际上是可变的 . 微软只是通过建议readonly字段由不可变对象支持来推荐反对这种微妙的,违反直觉的行为 .

  • 2

    agree with you completely ,我 do 有时在我的代码中使用 readonly 来获取可变引用类型 .

    举个例子:我可能有一些 privateprotected 成员 - 比方说, List<T> - 我在一个类的方法中使用它的所有可变荣耀(调用 AddRemove 等) . 我可能只是想制定一个保障措施,以确保无论如何, I am always dealing with the same object . 这可以保护我和其他开发人员不做一些愚蠢的事情:即将成员分配给新对象 .

    对我来说,这通常是使用具有私有 set 方法的属性的首选替代方法 . 为什么?因为 readonly 表示 the value cannot be changed after instantiation, even by the base class .

    换句话说,如果我有这个:

    protected List<T> InternalList { get; private set; }
    

    然后我仍然可以在我的基类的代码中的任意点设置 InternalList = new List<T>(); . (这对我来说需要一个非常愚蠢的错误,是的;但它仍然是可能的 . )

    另一方面,这个:

    protected readonly List<T> _internalList;
    

    使 unmistakably clear _internalList 只能引用一个特定对象(构造函数中设置了 _internalList 的对象) .

    所以我就在你身边 . 一个人应该避免在可变引用类型上使用 readonly 的想法让我个人感到沮丧,因为它基本上预先假定了对 readonly 关键字的误解 .

  • 25

    最后,它们只是指导方针 . 我知道微软的人通常不遵循所有指导原则 .

相关问题