代码之家  ›  专栏  ›  技术社区  ›  Weeble

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

  •  37
  • Weeble  · 技术社区  · 15 年前

    Design Guidelines for Developing Class Libraries ,微软说:

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

    使用可变类型创建的对象可以在创建后进行修改。例如,数组和大多数集合是可变类型,而int32、uri和string是不可变类型。对于包含可变引用类型的字段,只读修饰符防止覆盖字段值,但不保护可变类型不被修改。

    这只是简单地重述readonly的行为,而没有解释为什么使用readonly是不好的。其含义似乎是许多人不理解“readonly”的作用,错误地期望readonly字段是不可变的。实际上,它建议使用“readonly”作为表示深度不可变性的代码文档(尽管编译器没有办法强制执行这一点),并禁止将其用于其正常函数:以确保在构造对象后字段的值不会更改。

    我对使用“readonly”来表示编译器所理解的正常含义之外的内容感到不安。我觉得它会鼓励人们误解“readonly”的含义,并且期望它意味着代码作者可能不想看到的东西。我认为它排除了在可能有用的地方使用它,例如显示两个可变对象之间的某种关系在其中一个对象的生命周期内保持不变。假设读者不理解“readonly”的含义,这一概念似乎也与微软的其他建议(如fxcop)相矛盾。 "Do not initialize unnecessarily" 规则,它假定您的代码的读者是语言方面的专家,并且应该知道(例如)bool字段自动初始化为false,并阻止您提供显示“是的,这已被有意识地设置为false;我没有忘记初始化它”的冗余。

    所以,首先, 为什么微软建议不要对可变类型的引用使用readonly? 我还想知道:

    • 你在所有的代码中都遵循这个设计准则吗?
    • 当你在一段你没有写的代码中看到“readonly”时,你期望得到什么?
    6 回复  |  直到 15 年前
        1
  •  21
  •   Dan Tao    15 年前

    完全同意你的意见 和我 有时使用 readonly 在我的可变引用类型代码中。

    举个例子:我可能有一些 private protected 成员——比如说, List<T> --在一个类的方法中,我用它的所有可变的荣耀(调用 Add , Remove 等)。我可能只是想设置一个安全措施来确保,无论发生什么, 我总是处理同一个对象 . 这可以防止我和其他开发人员做一些愚蠢的事情:即,将成员分配给一个新对象。

    对我来说,这通常是比使用私人财产更好的选择。 set 方法。为什么?因为 只读 方法 该值在实例化后不能更改,即使是由基类 .

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

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

    那我还是可以设置 InternalList = new List<T>(); 在我的基类中的任意代码点上。(这需要我犯一个非常愚蠢的错误,是的,但这仍然是可能的。)

    另一方面,这:

    protected readonly List<T> _internalList;
    

    使它 清晰无误 那个 _internalList 只能永远 指一个特定对象(指 内奸者 在构造函数中设置)。

    所以我站在你这边。人们应该避免使用 只读 对于我个人来说,一个可变的引用类型是令人沮丧的,因为它基本上假定了对 只读 关键字。

        2
  •  25
  •   Anthony Pegram    15 年前

    很自然,如果一个字段是只读的,您将不能更改该值或 有什么关系吗 . 如果我知道酒吧是foo的一个只读字段,我显然不能说

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

    但我可以说

    foo.Bar.Name = "Blah";
    

    如果对象的支撑条实际上是可变的。微软只是建议用不可变的对象来支持只读字段,以此来反对这种微妙的、违反直觉的行为。

        3
  •  7
  •   stakx - no longer contributing Saravana Kumar    15 年前

    将可变类型的实例分配给 readonly 领域。

    我看了一下 框架设计指南 书(第161-162页),它基本上说明了你已经注意到了什么。Joe Duffy的另一个评论解释了指导方针的理由-D’raison-d’t——tre:

    本指南试图保护您不受影响的是,您相信自己暴露了一个深度不可变的对象图,而实际上它很浅,然后编写代码来假设整个图是不可变的。
    - Joe Duffy

    我个人认为关键词 只读 名字不好。 它只指定引用的常量,而不指定被引用对象的常量,这一事实很容易造成误导。

    我想最好是 只读 使被引用对象也是不可变的,而不仅仅是引用,因为这就是关键字的含义。

    为了补救这种不幸的情况,制定了指导方针。我认为它的建议是合理的 人类 观点(并不总是明显的,哪些类型是可变的,而且它们不是没有查找它们的定义,并且这个词暗示了深度不变),我有时希望,当涉及到声明常量时,C ^将提供与C++所提供的类似的自由,在这里你可以定义。 const 要么在指针上,要么在指向对象上,要么两者都在,要么什么都不在。

        4
  •  2
  •   Vilx-    15 年前

    微软有一些这样特别的建议。另一个立即想到的是不要在公共成员中嵌套泛型类型,比如 List<List<int>> . 我尽量避免在可能的情况下使用这些结构,但是当我觉得使用是合理的时候,忽略新手友好的建议。

    至于只读字段,我尽量避免使用公共字段,而不是使用属性。我认为这方面也有一些建议,但更重要的是,有时当一个字段不起作用而属性起作用时(主要与数据绑定和/或可视化设计器有关)。通过使所有公共领域的财产,我避免任何潜在的问题。

        5
  •  1
  •   tster    15 年前

    最后,它们只是指导方针。我知道一个事实,微软的员工经常不遵守所有的指导方针。

        6
  •  1
  •   Hans Passant    15 年前

    您正在寻找的语法是由C++/CLI语言支持的:

    const Example^ const obj;
    

    第一个常量使被引用对象不可变,第二个常量使引用不可变。后者相当于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只是不支持它。