保证不变性是公开字段而不是公开属性的理由吗?


10

C#的一般指导原则是始终在公共领域使用属性。这很有意义-通过公开一个字段,您将公开许多实现细节。通过一个属性,您可以封装该详细信息,从而使其在使用代码中不会被隐藏,并且实现更改与接口更改脱钩。

但是,我想知道在处理readonly关键字时有时是否存在此规则的有效例外。通过将此关键字应用于公共领域,可以额外保证:不变性。这不仅是实现细节,不变性是用户可能感兴趣的东西。使用readonly字段使其成为公共合同的一部分,并且在将来的更改或继承中也不会破坏,而不必修改公共接口。那是财产所不能提供的。

那么,readonly在某些情况下,确保不变性是在属性上选择字段的合法理由吗?

(为澄清起见,我当然不是说您应该总是仅因为该字段恰好是不可变的而做出此选择,只有当该字段作为类设计的一部分是有意义的并且打算将不可变性包括在其合同中时才使用。我最感兴趣的答案是侧重于此是否合理,而不是在某些情况下不合理(例如,当您需要将该成员放在interface或希望进行延迟加载时)。



1
@gnat通过“ ReadOnly属性”来澄清,他们使用的是VB.NET术语,这意味着没有设置程序的属性,而不是只读字段。就我的问题而言,该问题比较的两个选项是等效的-它们都使用属性。
Ben Aaronson

Answers:


6

公共静态只读字段当然可以。建议在某些情况下使用它们,例如当您需要一个命名的常量对象但不能使用const关键字时。

只读成员字段有些棘手。没有公共设置器的属性没有太多好处,而且还有一个主要的缺点:字段不能是接口的成员。因此,如果您决定重构代码以使其针对接口而非具体类型进行操作,则必须将只读字段更改为具有公共getter的属性。

没有受保护的setter的公共非虚拟属性可以防止继承,除非继承的类可以访问后备字段。您可以在基类中完全执行该合同。

在这种情况下,不更改类的公共接口就不能更改成员的“不变性”并不是很大的障碍。通常,如果要将公共字段更改为属性,则除了需要处理两种情况外,它很顺利:

  1. 任何写入该对象成员的内容,例如:object.Location.X = 4仅当Location为字段时才可能。但是,这与只读字段无关,因为您不能修改只读结构。(这是假设Location是一种值类型-如果不是,则无论如何readonly都不会出现此问题,因为它无法防止此类情况发生。)
  2. 对传递值out或的方法的任何调用ref。再次,这与readonly字段并不真正相关,因为outref参数往往会被修改,并且将readonly值作为out或ref传递仍然是编译器错误。

换句话说,尽管将只读字段转换为只读属性会破坏二进制兼容性,但是只需重新编译其他源代码,而无需进行任何更改即可处理此更改。这使得保证确实很容易被打破。

我看不到readonly成员字段有任何优势,并且无法在接口上拥有这种东西对我来说是一个劣势,因为我不会使用它。


出于兴趣,为什么公共静态只读域好吗?仅仅是因为排除实例方法会删除不可变字段上的属性的大多数用例(例如,命中计数,延迟加载等)?
Ben Aaronson

公共只读字段与只读属性的主要缺点已经消失了-静态成员不能成为接口的一部分,因此它们处于相似的境地。只读静态属性要么需要进行初始化的存储,要么需要在每次调用时重建其返回值,而只读字段的语法将更简单,并由静态构造方法初始化。因此,我认为公共静态只读字段具有通过JIT进行更大优化的潜力,而没有暴露字段通常具有的缺点。
Erik

2

根据我的经验,readonly关键字并不是它所承诺的魔法。

例如,您有一个构造函数曾经很简单的类。给定用法(以及构造后某些类属性/字段不可变的事实),您可能会认为这没有关系,因此您使用了readonly关键字。

后来,类变得更加复杂,其构造函数也变得更加复杂。(比方说,这发生在实验性项目或超出范围/控制的足够高速度缩放的项目中,但是您需要使其以某种方式起作用。)您会发现readonly只能在构造函数中修改的字段-您即使仅从构造函数中调用该方法,也无法在方法中对其进行修改。

此技术限制已为C#设计人员所认可,并可能在将来的版本中修复。但是,在修正之前,使用readonly更加严格,可能会鼓励某些不良的编码风格(将所有内容放入构造方法中)。

提醒一下,readonlynon-public-setter属性和non-public-setter属性都不能保证“完全初始化的对象”。换句话说,是由一个类的设计者来决定“完全初始化”的含义,以及如何保护它免遭无意使用的影响,并且这种保护通常不是万无一失的,这意味着有人可能会找到解决方法。


1
我宁愿保持构造简单-看brendan.enrick.com/post/...blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple所以实际上,只读鼓励良好的编码风格
AlexFoxGill

1
构造函数可以将readonly字段作为outref参数传递给其他方法,然后其他方法可以随意修改它们。
2015年

1

字段始终是实施细节。您不想公开实现细节。

软件工程的基本原则是,我们不知道将来的需求是什么。尽管您的readonly领域今天可能很好,但是没有任何迹象表明它将成为明天需求的一部分。如果明天需要实施点击计数器来确定对该字段进行了多少次访问,该怎么办?如果需要允许子类覆盖该字段怎么办?

属性是如此易于使用,并且比公共字段灵活得多,以至于我无法想到一个用例,在这种情况下,我既会选择c#作为语言,又会在类上使用公共只读字段。如果性能太紧,以至于我无法接受额外的方法调用,那么我可能会使用其他语言。

仅仅因为我们可以用语言做某事并不意味着我们应该用语言做某事。

我实际上想知道语言设计者是否后悔允许将字段公开。我不记得我上次考虑在代码中使用非常量公共字段了。


1
我确实了解构建未来灵活性的重要性,但是,如果我编写一个Rational带有public,不变NumeratorDenominator成员的类,我肯定会非常有信心,这些成员将不需要延迟加载或点击计数器或类似?
Ben Aaronson

但是您是否可以确定将float类型Numerator和一起使用的实现Denominator不需要更改为doubles
斯蒂芬

1
好吧,嗯,不,他们绝对不应该也不floatdouble。他们应该是intlongBigInteger。也许这些需要更改...但是,如果是这样,它们也需要在公共接口中进行更改,因此实际上使用属性不会对该细节进行任何封装。
Ben Aaronson

-3

不,这不是正当的理由。

在没有理由的情况下将readonly作为不变性的保证,更像是hack。

只读表示在定义变量时由构造函数o分配值。

我认为这将是一个坏习惯

如果您真的想确保不变性,请使用接口,而不是缺点。

简单的界面示例: 在此处输入图片说明


1
“使用界面”如何?
Ben Aaronson
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.