Getter 和 Setter 应该做啥和不应该做啥
Posted
技术标签:
【中文标题】Getter 和 Setter 应该做啥和不应该做啥【英文标题】:What Getters and Setters should and shouldn't do [duplicate]Getter 和 Setter 应该做什么和不应该做什么 【发布时间】:2011-02-23 21:42:04 【问题描述】:可能重复:Convention question: When do you use a Getter/Setter function rather than using a Property
我最近在 Getter 和 Setter 上遇到了很多不同的意见,所以我想我应该把它变成自己的问题。
我的一个previous question 立即收到了一条评论(后来被删除),指出setter 不应该有任何副作用,SetProperty
方法会是更好的选择。
确实,这似乎也是Microsoft's opinion。但是,它们的属性通常会引发事件,例如当设置了表单的Width
或Height
属性时,会引发Resized
事件。 OwenP 还声明“你不应该让属性抛出异常,属性不应该有副作用,顺序不重要,并且属性应该相对快速地返回。”
然而Michael Stum 声明在 setter 中验证数据时应该抛出异常。如果您的 setter 没有抛出异常,您如何有效地验证数据,正如this question 的许多答案所建议的那样?
当您需要引发事件时怎么办,就像几乎所有 Microsoft 的 Control 所做的那样?那么,您是否不受订阅您活动的人的摆布?如果他们的处理程序执行大量信息,或者自己抛出错误,你的 setter 会发生什么?
最后,getter 中的lazy loading 呢?这也可能违反以前的准则。
什么可以放在 getter 或 setter 中,什么应该只保留在访问器方法中?
编辑:
来自 MSDN 中的另一个 article:
get
和set
方法通常与其他方法没有区别。它们可以执行任何程序逻辑、抛出异常、被覆盖以及使用编程语言允许的任何修饰符进行声明。但是请注意,属性也可以是静态的。如果属性是静态的,则get
和set
方法可以做什么是有限制的。有关详细信息,请参阅您的编程语言参考。
【问题讨论】:
我阅读了那个问题、答案,甚至在我自己的问题中链接到它。我没有看到任何关于引发事件的信息,我认为这值得不同的答案。 @Rowland:我认为它已经足够具体了。 谁投票支持将此迁移到超级用户? 它与许多其他问题有关,但不是完全重复,因为它涵盖了新领域。 我确实找到的其他问题的数量,我也链接了,这样人们就可以看到我认为没有决定性的信息,而不仅仅是将我链接到问题我已经阅读或将其作为副本关闭。显然我只是让他们的工作更轻松。 【参考方案1】:我的看法:
如果预计 setter 或 getter 会很昂贵,请不要将其设为属性,而应将其设为方法。
如果设置属性由于更改而触发事件,这很好。您还如何允许听众收到更改通知?但是,您可能希望提供 BeginInit/EndInit 对来抑制事件,直到进行所有更改。通常,事件处理程序有责任及时返回,但如果您真的不相信它会这样做,那么您可能希望在另一个线程中发出事件信号。
如果设置属性会在无效值上引发异常,也可以。这是在值完全错误时发出问题信号的合理方式。在其他情况下,您设置一堆属性,然后调用使用它们做某事的方法,例如建立连接。这将允许在使用属性之前推迟验证和错误处理,因此属性不需要抛出任何东西。
访问属性可能会产生副作用,只要它们不是意外且无关紧要。这意味着 getter 中的 JIT 实例化很好。同样,每当进行更改时为实例设置一个脏标志就可以了,因为它设置了相关属性,例如相同值的不同格式。
如果它做某事而不是仅仅访问一个值,它应该是一个方法。方法是动词,因此创建连接将由 OpenConnection() 方法完成,而不是 Connection 属性。 Connection 属性将用于检索正在使用的连接,或将实例绑定到另一个连接。
编辑 - 添加 5,更改 2 和 3
【讨论】:
【参考方案2】:我同意 getter/settings 不应该有副作用的想法,但我会说它们不应该有不明显的副作用。
就抛出异常而言,如果您将属性设置为无效值(在非常基本的意义上),那么验证异常就可以了。但是,如果 setter 正在运行一系列复杂的业务规则验证,或者试图关闭并更新其他对象,或者任何其他可能导致异常的事情,那就不好了。但是这个问题并不是异常本身的问题,而是设置器正在关闭并秘密执行调用者不会(或不应该)期望的许多功能。
事件也是如此。如果一个 setter 抛出一个事件说“这个属性改变了”,那么没关系,因为这是一个明显的副作用。但是,如果它触发了其他一些自定义事件,从而导致一些隐藏的代码在系统的另一部分执行,那就很糟糕了。
这与我避免在 getter 中延迟加载的原因相同。事实上,它们在很多时候可以让事情变得更容易,但在某些时候它们会让事情变得更加混乱,因为在你想要加载子对象的时候总是会出现复杂的逻辑。当您填充父对象时,通常只需要多行代码来显式加载子对象,并且可以避免对对象状态的很多混淆。但这方面可能会变得非常主观,而且很大程度上取决于具体情况。
【讨论】:
避免懒惰会带来巨大的性能成本,所以我认为总的来说这不是一个好主意。 @Steven Sundit:是的,这是一种权衡,这取决于具体情况。如果它的预加载引入了大量的性能成本,那么就追求延迟加载或更具体的加载。但通常情况下(根据我的经验,通常情况下)并非如此。 预加载当然是一个合适的选择,尤其是在廉价或一次性的情况下。如果两者都不是,那么 JIT 就变成了强制措施。【参考方案3】:无论如何,在使用 C# 工作时,我一直认为保守的方法是最好的。因为属性在语法上与字段相同,所以它们应该像字段一样工作:没有异常,没有验证,没有有趣的事情。 (事实上,我的大部分属性都是从简单的字段开始的,直到绝对必要时才成为属性。)这个想法是,如果你看到看起来像是在获取或设置字段集的东西,那么它就像获取或设置字段集一样。设置一个字段,根据功能(不会抛出异常)、整体效率(例如,设置变量不会触发级联的委托调用)和对程序状态的影响(设置一个变量会设置该变量,并且不会)不会调用很多可以做任何事情的代表)。
属性集要做的明智的事情包括设置一个标志来指示发生了变化:
set
if(this.value!=value)
this.changed=true;
this.value=value;
也许实际上是在另一个对象上设置了一个值,例如:
set this.otherObject.value=value;
也许可以稍微解开输入,以简化类的内部代码:
set
this.isValid=(value&Flags.IsValid)!=0;
this.setting=value&Flags.SettingMask;
(当然,在后两种情况下,get 函数可能正好相反。)
如果需要发生更复杂的事情,特别是调用委托、执行验证或抛出异常,那么我的观点是函数更好。 (很多时候,我的字段通过 get 和 set 变成属性,然后最终成为 get 属性和 set 函数。)对于 getter 也是如此;如果您要返回对某物的引用,那没问题,但如果您要创建一个全新的大对象并在每次读取该属性时填充它——就不那么热了。
【讨论】:
如果你在 C# 中工作,那么你必须了解 WPF,它完全依赖于属性更改,具有发出这些更改信号的副作用。我看不出我们如何在不破坏代码的情况下遵循您的建议。 当您说“发出这些变化信号的副作用”时,您指的是引发事件吗?或者你是说if(this.value!=value) return;
是个坏主意?我没有使用过WPF。 (虽然我用 C# 编写程序。)
@Cyclotis:嗯,这不仅仅适用于 WPF,但 WPF 特别明显地使用了属性更改通知。您还会在许多使用数据绑定的地方找到示例。如果我们不允许在发生变化时触发事件,我们有什么来代替?投票?
我还是有点困惑 - C# 不会自动生成属性更改通知,是吗?您仍然必须手动引发事件,对吗?我的大多数二传手都有以下内容:if (State == value) return; _state = value; if (StateChanged != null) OnStateChanged(EventArgs.Empty);
@Cyclotis:不,它不是自动的,但对于某些类型的绑定是强制性的。见msdn.microsoft.com/en-us/library/ms743695.aspx以上是关于Getter 和 Setter 应该做啥和不应该做啥的主要内容,如果未能解决你的问题,请参考以下文章
我应该如何在 Firebase 实时数据库中设置作为键值类型的 getter 和 setter?
该属性必须是有效的实体类型,并且该属性应该具有非抽象的 getter 和 setter。实体框架 [重复]