属性应该有副作用吗


19

除了通知状态改变外,C#中的属性是否还会有副作用?

我已经看到以几种不同方式使用属性。从将在首次访问值时加载值的属性到具有大量副作用(例如导致重定向到其他页面)的属性。



1
在@Job的发现中,为什么要避免在C#中使用Properties?aka Jeffrey Richter的观点与此问题非常相关。
rwong 2011年

@rwong相关,是的,但完全没有意义-仍然必须阅读(以了解他强调的问题)。至少在这个问题上,我在斯基特(Skeet)集中营。
Apoorv Khurasia 2013年

尽管这与构造函数有关,但这也很重要:programmers.stackexchange.com/a/164255/1996
Jim G.

Answers:


40

我假设您是在谈论只读属性,或者至少是属性getters,因为在几乎所有情况下,属性设置器都会产生副作用。否则,它不是很有用。

通常,一个好的设计遵循最小的惊喜原则。不要做呼叫者不希望您做的事情,尤其是当这些事情可能改变未来的结果时。

通常,这意味着属性获取器不应具有副作用。

但是,请注意“副作用”的含义

从技术上讲,副作用是对状态的任何修改。那可能是可公开访问的状态,或者……可能是完全私有的状态。

延迟/延迟加载程序是状态的一种示例,几乎完全是私有的。只要释放资源不是调用者的责任,那么实际上您通常通过使用延迟初始化来减少意外和复杂性。调用者通常不期望必须显式表示内部结构的初始化。因此,延迟初始化并没有违反上述原则。

另一个示例是同步属性。为了使方法具有线程安全性,通常必须使用临界区或互斥体对其进行保护。进入关键部分或获取互斥体副作用。您正在修改状态,通常是全局状态。但是,此副作用是必需的,以防止发生更糟糕的意外情况-数据被另一个线程修改(或更糟的是部分修改)的意外情况。

因此,我将对以下内容的限制放宽一些:属性读取不应具有可见的副作用或改变其语义的副作用。

如果呼叫者不可能受到副作用的影响,甚至意识不到副作用,那么该副作用就不会造成任何伤害。如果您不可能编写测试来验证特定副作用的存在,那么它已经足够本地化以标记为私有实现细节,因此对外界没有任何合法关注。

但是要小心;根据经验,应该尝试避免产生副作用,因为通常您认为是私有实现细节的内容可能会意外泄漏并公开。


2
+
1-

吸气剂可接受的副作用的另一个示例:记录功能。
罗伦·佩希特尔

5

我将用一个问题回答您的问题:修改表单的Width属性时会发生什么?

就在我的头顶上,这将:

  • 更改背景字段的值。
  • 触发事件。
  • 更改窗体的尺寸,将更改报告给窗口管理器,然后请求重新绘制。
  • 通知更改形式的控件,以便在调整表单大小时需要更改大小或位置的任何控件都可以这样做。
  • 导致所有更改为激发事件的控件,并告诉窗口管理器它们需要重绘。
  • 可能还有其他东西。

(免责声明:我是Delphi开发人员,而不是.NET开发人员。对于C#来说,这可能不是100%准确,但是我敢打赌,这非常接近,尤其是考虑到原始.NET框架中有多少基于Delphi。)

当您更改窗体的Width属性时,所有这些“副作用”都会发生。他们中的任何一个看起来不合适还是不正确?


只要它不会进入调用自己的无限循环。为了避免这种情况,每个“更改”将映射到至少三个事件:一个在更改之前触发,一个在更改期间触发,另一个在完成后触发。
rwong 2011年

5

看一下Microsoft设计规则,尤其是其中之一:

CA1024:在适当的地方使用属性

...如果存在以下条件之一,则方法是成为属性的一种很好的选择:

  • 不带参数,并返回对象的状态信息。
  • 接受单个参数来设置对象状态的某些部分。

属性应表现为字段。如果方法不能,则不应将其更改为属性。在以下情况下,方法胜于属性:

  • 该方法执行耗时的操作。可以感觉到,该方法比设置或获取字段值所需的时间慢。
  • 该方法执行转换。访问字段不会返回其存储的数据的转换版本。
  • Get方法具有明显的副作用。检索字段的值不会产生任何副作用。
  • 执行顺序很重要。设置字段的值不依赖于其他操作的发生。
  • 连续两次调用该方法会产生不同的结果。
  • 该方法是静态的,但返回一个可由调用者更改的对象。检索字段的值不允许调用者更改该字段存储的数据。
  • 该方法返回一个数组...

2

我相信属性不应有副作用。但是,此规则有一个例外:延迟加载。如果您想延迟加载属性中的某些内容,那很好,因为客户端无法告知延迟加载正在进行,并且通常不会在乎。

编辑:哦,还有一件事-触发一个事件,说“ PropertyXYZ已更改!” (例如INotifyPropertyChanged)-对二传手很好。



2

编程中几乎没有绝对值,为了回答您的问题,我们需要查看对象的某些理想属性,然后看看这是否适用于我们的情况

  1. 我们希望类易于测试。
  2. 我们希望类支持并发
  3. 我们希望让类易于推理

如果我们对某些问题回答是肯定的,那么仔细考虑一下特性及其副作用可能是个好主意。我认为,当您说副作用时,因为更新值本身就是副作用,所以它是指次要副作用。

通常,副作用越多,一堂课的考试就越困难。次要副作用越多,并发处理就越困难,但这是一个棘手的问题,无论如何它可能需要一些主要的设计思想。

大问题可能是针对那些将您的班级视为“黑匣子”的人,某些状态因为仅仅读了一个属性而改变,或者某些其他属性却因另一个改变而改变(这可能导致级联更新)。

因此,总的来说,我们不希望属性尽可能容易地推理和简单化,这在设计决策中就已隐含了,否则它将是一种方法。一个好的程序员总是可以违反规则,只是要确保您有一个非常非常好的理由:)

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.