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

发布于 2024-08-31 14:54:10 字数 1031 浏览 3 评论 0 原文

开发类库的设计指南中,微软说:

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

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

这只是重申了 readonly 的行为,而没有解释为什么使用 readonly 不好。这似乎意味着许多人不理解“只读”的作用,并且错误地期望只读字段是深度不可变的。实际上,它建议使用“只读”作为指示深度不变性的代码文档 - 尽管事实上编译器无法强制执行此操作 - 并且不允许将其用于其正常功能:以确保字段的值在之后不会改变该对象已被构造。

我对使用“只读”来指示编译器理解的正常含义之外的内容的建议感到不安。我觉得它鼓励人们误解“只读”的含义,并进一步期望它意味着代码作者可能无意的东西。我觉得它阻止在可能有用的地方使用它 - 例如,表明两个可变对象之间的某些关系在其中一个对象的生命周期内保持不变。假设读者不理解“只读”含义的想法似乎也与 Microsoft 的其他建议相矛盾,例如 FxCop 的 "不必要地初始化" 规则,该规则假设代码的读者是该语言的专家,并且应该知道(例如)bool 字段会自动初始化为 false,并阻止您提供显示“是的,这已被有意设置为 false;我不只是忘记初始化它”的冗余。

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

  • 您在所有代码中都遵循此设计指南吗?
  • 当您在一段不是您编写的代码中看到“readonly”时,您会期待什么?

In the Design Guidelines for Developing Class Libraries, Microsoft say:

Do not assign instances of mutable types to read-only fields.

The objects created using a mutable type can be modified after they are created. For example, arrays and most collections are mutable types while Int32, Uri, and String are immutable types. For fields that hold a mutable reference type, the read-only modifier prevents the field value from being overwritten but does not protect the mutable type from modification.

This simply restates the behaviour of readonly without explaining why it's bad to use readonly. The implication appears to be that many people do not understand what "readonly" does and will wrongly expect readonly fields to be deeply immutable. In effect it advises using "readonly" as code documentation indicating deep immutability - despite the fact that the compiler has no way to enforce this - and disallows its use for its normal function: to ensure that the value of the field doesn't change after the object has been constructed.

I feel uneasy with this recommendation to use "readonly" to indicate something other than its normal meaning understood by the compiler. I feel that it encourages people to misunderstand the meaning of "readonly", and furthermore to expect it to mean something that the author of the code might not intend. I feel that it precludes using it in places it could be useful - e.g. to show that some relationship between two mutable objects remains unchanged for the lifetime of one of those objects. The notion of assuming that readers do not understand the meaning of "readonly" also appears to be in contradiction to other advice from Microsoft, such as FxCop's "Do not initialize unnecessarily" rule, which assumes readers of your code to be experts in the language and should know that (for example) bool fields are automatically initialised to false, and stops you from providing the redundancy that shows "yes, this has been consciously set to false; I didn't just forget to initialize it".

So, first and foremost, why do Microsoft advise against use of readonly for references to mutable types? I'd also be interested to know:

  • Do you follow this Design Guideline in all your code?
  • What do you expect when you see "readonly" in a piece of code you didn't write?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

哀由 2024-09-07 14:54:10

如果一个字段是只读的,您会期望无法更改该值或与之相关的任何内容,这似乎很自然。如果我知道 Bar 是 Foo 的只读字段,我显然不能说,

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

但我可以说“

foo.Bar.Name = "Blah";

如果支持 Bar 的对象实际上是可变的”。微软只是建议只读字段由不可变对象支持,从而反对这种微妙的、违反直觉的行为。

It seems natural that if a field is readonly, you would expect to not be able to change the value or anything having to do with it. If I knew that Bar was a readonly field of Foo, I could obviously not say

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

But I can get away with saying

foo.Bar.Name = "Blah";

If the object backing Bar is, in fact, mutable. Microsoft is simply recommending against that subtle, counterintuitive behavior by suggesting that readonly fields be backed by immutable objects.

风铃鹿 2024-09-07 14:54:10

完全同意您的观点,并且我确实有时会在我的代码中使用readonly来表示可变引用类型。

举个例子:我可能有一些privateprotected成员——比如说,一个List——我在类的内部使用它方法的所有可变荣耀(调用 AddRemove 等)。我可能只是想设置一个保护措施,以确保无论如何,我总是处理同一个对象。这可以保护我和其他开发人员不会做一些愚蠢的事情:即将成员分配给一个新对象。

对我来说,这通常是使用私有 set 方法的属性的更好替代方案。为什么?因为readonly意味着实例化后该值不能更改,即使是基类也不能更改

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

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

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

另一方面,这:

protected readonly List<T> _internalList;

明确无误表明 _internalList < em>只能引用一个特定对象(在构造函数中设置 _internalList 的对象)。

所以我站在你这边。对于我个人来说,应该避免在可变引用类型上使用 readonly 的想法令人沮丧,因为它基本上预设了对 readonly 关键字的误解。

I agree with you completely, and I do sometimes use readonly in my code for mutable reference types.

As an example: I might have some private or protected member -- say, a List<T> -- which I use within a class's methods in all its mutable glory (calling Add, Remove, etc.). I may simply want to put a safeguard in place to ensure that, no matter what, I am always dealing with the same object. This protects both me and other developers from doing something stupid: namely, assigning the member to a new object.

To me, this is often a preferable alternative to using a property with a private set method. Why? Because readonly means the value cannot be changed after instantiation, even by the base class.

In other words, if I had this:

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

Then I could still set InternalList = new List<T>(); at any arbitrary point in code in my base class. (This would require a very foolish error on my part, yes; but it would still be possible.)

On the other hand, this:

protected readonly List<T> _internalList;

Makes it unmistakably clear that _internalList can only ever refer to one particular object (the one to which _internalList is set in the constructor).

So I am on your side. The idea that one should refrain from using readonly on a mutable reference type is frustrating to me personally, as it basically presupposes a misunderstanding of the readonly keyword.

守望孤独 2024-09-07 14:54:10

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

我快速浏览了框架设计指南一书(第 161-162 页),它基本上说明了您自己已经注意到的内容。 Joe Duffy 的附加评论解释了该指南存在的理由:

本指南试图防止您相信自己暴露了一个深度不可变的对象图,而实际上它很浅,然后编写假设整个图是不可变的代码。 < br>——乔·达菲

我个人认为关键字readonly 的命名很糟糕。事实上,它只指定引用的常量,而不指定引用的常量。引用的对象,很容易造成误导的情况。

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

为了纠正这种不幸的情况,制定了该指南。虽然我认为从人类的角度来看它的建议是合理的(如果不查找它们的定义,并不总是很明显哪些类型是可变的,哪些不是,并且这个词暗示了深度不变性),但我有时希望,在声明 const 性时,C# 能够提供与 C++ 类似的自由,您可以在指针或指向的对象上定义 const ,或者两者皆有,或者什么都不做。

DO NOT assign instances of mutable types to readonly fields.

I had a quick look in the Framework Design Guidelines book (pages 161-162), and it basically states what you've already noticed yourself. There's an additional comment by Joe Duffy that explains the guideline's raison-d'être:

What this guideline is trying to protect you from is believing you've exposed a deeply immutable object graph when in fact it is shallow, and then writing code that assumes the whole graph is immutable.
— Joe Duffy

I personally think that the keyword readonly was named badly. The fact that it only specifies the const-ness of the reference, and not of the const-ness of the referenced object, easily creates misleading situations.

I think it would have been preferable if readonly made referenced objects immutable, too, and not just the reference, because that is what the keyword implies.

To remedy this unfortunate situation, the guideline was made up. While I think that its advice is sound from the human point of view (it's not always obvious which types are mutable and which aren't without looking up their definition, and the word suggests deep immutability), I sometimes wish that, when it comes to declaring const-ness, C# would offer a freedom similar to that offered by C++, where you can define const either on the pointer, or on the pointed-to-object, or on both or nothing.

长亭外,古道边 2024-09-07 14:54:10

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

const Example^ const obj;

第一个 const 使引用的对象不可变,第二个 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 不支持它。

The syntax you are looking for is supported by the C++/CLI language:

const Example^ const obj;

The first const makes the referenced object immutable, the 2nd makes the reference immutable. The latter is equivalent to the readonly keyword in C#. Attempts to evade it produce a compile error:

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; 

It is however smoke and mirrors. The immutability is verified by the compiler, not by the CLR. Another managed language could modify both. Which is the root of the problem, the CLR just doesn't have support for it.

我恋#小黄人 2024-09-07 14:54:10

微软有一些这样奇特的建议。立即浮现在脑海中的另一个问题是不要在公共成员中嵌套泛型类型,例如 List>。我会尽量避免使用这些结构,但当我认为使用合理时,会忽略对新手友好的建议。

至于只读字段 - 我尝试避免此类公共字段,而是使用属性。我认为对此也有一条建议,但更重要的是,有时会出现字段不起作用而属性起作用的情况(主要与数据绑定和/或视觉设计器有关)。通过将所有字段设置为公共属性,我可以避免任何潜在的问题。

Microsoft has a few such peculiar advices. The other one that immediately springs to mind is not to nest generic types in public members, like List<List<int>>. I try to avoid these constructs where easily possible, but ignore the newbie-friendly advice when I feel the use is justified.

As for readonly fields - I try to avoid public fields as such, instead going for properties. I think there was a piece of advice about that too, but more importantly there are cases now and then when a field doesn't work while a property does (mostly it has to do with databinding and/or visual designers). By making all public fields properties I avoid any potential problems.

爱的故事 2024-09-07 14:54:10

最终它们只是指导方针。我知道 Microsoft 的员工通常不会遵守所有准则。

In the end they are just guidelines. I know for a fact that the people at Microsoft often don't follow all of the guidelines.

挽清梦 2024-09-07 14:54:10

这在 C#(简单控制台应用程序)中是合法的,

readonly static object[] x = new object[2] { true, false };
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
        x[0] = false;
        x[1] = true;

        Console.WriteLine("{0} {1}", x[0], x[1]); //prints "false true"
        Console.ReadLine();
    }

可以工作。但这没有意义。请记住变量 x 是只读的,并且没有改变(即 x 的引用确实没有改变)。但这不是我们说“只读 x”时的意思,不是吗?因此不要使用具有可变值的只读字段。这是令人困惑和违反直觉的。

This is legal in C# (simple Console App)

readonly static object[] x = new object[2] { true, false };
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
        x[0] = false;
        x[1] = true;

        Console.WriteLine("{0} {1}", x[0], x[1]); //prints "false true"
        Console.ReadLine();
    }

that would work. but that wouldn't make sense. bear in mind the variable x is readonly, and has not changed (i.e. the ref of x has not changed indeed). but that's not what we meant when we said "readonly x", is it? so don't use readonly fields with mutable values. It's confusing and counter-intuitive.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文