DateTime.Now为什么是属性而不是方法?

60
在阅读了这篇博客文章之后:http://wekeroad.com/post/4069048840/when-should-a-method-be-a-property,我想知道为什么Microsoft在C#中选择:
DateTime aDt = DateTime.Now;

而不是

DateTime aDt = DateTime.Now();
  • 最佳实践指出:当在连续调用成员两次时会产生不同结果时,请使用方法。
  • DateTime.Now 是不确定的方法/属性的完美例子。

你知道这个设计是否有任何原因吗?
还是只是一个小错误?


8
时间只是一种幻觉:一切都在此时此刻发生。因此,时间是一种属性 :) - Paolo Falabella
3
相对而言,这个值从未改变;它总是“现在”。如果该结构体的字段包含不同的数字,则该值将相对于“现在”而改变!啊..脑子..疼.. - Andrew Barber
1
@Andrew:那么它应该返回一个 Func<DateTime> 吗? :) - Brian
6
我写了一个程序,使用一个循环调用DateTime.Now等待它改变。我成功地连续调用了16000多次而没有改变。使用UtcNow,我连续调用了超过100万次才改变。我认为这足以将其作为一个属性。 - Gabe
2
@Gebe,我建议达成一项协议:“如果连续调用该方法42次返回相同的值,则可以将其转换为属性。” - Vitaliy Ulantikov
显示剩余2条评论
6个回答

50

我相信《CLR via C#》一书中提到,DateTime.Now 属性是一个错误的设计。

System.DateTime 类有一个只读的 Now 属性,它返回当前日期和时间。每次查询此属性时,它都会返回一个不同的值。这是一个错误,微软希望他们可以通过将 Now 改为方法来修复该类。

《CLR via C# 第三版》- 243页


2
我一直认为这是一个错误,总是先写Now(),叹气至少一次,然后再按两次退格键。 - stefan
12
我尊重Jeffrey和他的观点,但我认为他并没有引用来源,这让我觉得他是在做自己的解释。DateTime.Now是一个特殊情况,但它在其他方面符合准则。即使有可能,我也不认为他们会更改它。否则,他们会弃用它并引入一个新方法。 - Erik Funkenbusch
3
我同意这是Jeffrey的解释,但我相信他,因为在一个类中不可能有相同名称的属性和方法(所以创建该方法并保留已弃用的属性是不可能的)。 - Jonas at Software Journey
6
同样的悲叹在《.NET框架设计指南》中也有出现。该书提供了更详细的解释,对于任何聪明到足以提出这个问题的人来说,这是一本必读的书籍。;-) - Cody Gray
3
你引用的书中摘录是关于作者对属性的个人观点的解释的一部分:“就我个人而言,我不喜欢属性,希望它们在微软.NET框架及其编程语言中不被支持。”我无法给出足够的理由来解释为什么这个声明在许多方面都是错误的。 - Viktor

7
它实际上是确定性的;它的输出不是随机的,而是基于某些相当可预测的东西。
“当前时间”随时都在变化;因此,为了保持每次调用时相对“相同”,该值必须改变,以便每次调用时返回当前时间。
编辑:
我突然想到:当然,连续两次调用属性getter可能会返回不同的结果,如果在中间有什么改变了属性值的东西。属性不应该是常量。
所以,这就是发生的事情(概念上)与DateTime.Now;在后续调用之间,其值正在被更改。

1
在现实世界中,时间是可以预测的,但在计算机上并非如此。 Now()是一个带有很多误差边界的函数。我不确定Now是如何指定的,但对于普通计时器/时钟,它可能会基于实现和硬件返回一个数字,以至于a = DatetTime.Now; b = DateTime.Now; (b-a) < 0.. - stefan
我们得到的输出可能并不是确定性的,但是从概念上讲,“现在”绝对是确定性的。虽然我对此没有强烈的看法,但我认为这是一个有趣的愚蠢的星期五下午的问题:P - Andrew Barber
我认为这实际上是一个非常好的和严肃的问题。属性的滥用必须结束!实际上,我并不支持属性。它们没有智能感知,很混乱,你最终会初始化你不需要的值,只是因为它“可能是下一个要更改的属性,然后它可能会导致除以零”等类似情况。 - stefan
1
@stefan - 无论如何,我还没有失眠的打算,也不指望会因此而失眠。 - Andrew Barber
1
我同意一个观点,时间会继续流逝,无论这个问题是否有答案... - Jonas at Software Journey
1
你对“确定性”的定义是错误的:“确定性函数在任何给定特定输入值的情况下,总是返回相同的结果”。这个词指的是仅凭函数定义和输入是否能够确定输出。使用 DateTime.Now 就无法做到这一点。详见:https://technet.microsoft.com/zh-cn/library/aa214775(v=sql.80).aspx - sara

4
根据MSDN的说法,当某个东西是对象的逻辑数据成员时,应该使用属性(property):http://msdn.microsoft.com/en-us/library/bzwdh01d%28VS.71%29.aspx#cpconpropertyusageguidelinesanchor1。接着,它们列出了使用方法更为合适的情况。讽刺的是,其中一个方法的规则是在连续调用中可能返回不同的结果,而Now显然符合这一标准。个人认为这是为了消除额外的()的需要,但我发现缺少()很令人困惑;我花了一点时间才从VB/VBA的旧方法中转变过来。

3
指南只是指导性的,并非硬性规定。
这些指南旨在针对有状态对象,实际上试图表达的是属性不应该改变对象。DateTime.Now是一个静态属性,因此调用它不会改变对象。它仅仅反映了时间的自然状态,没有改变任何东西。它只是观察一个不断变化的计时器。
因此,重点是不要创建改变对象状态的属性。确保创建的属性只是观察对象的状态(即使状态是由外部改变的)。
再举一个例子,让我们看看字符串的长度。这是一个属性,但如果其他东西在外部更改了字符串,则字符串的长度可能会在调用之间发生变化。这就是基本情况,计时器正在外部被更改,Now只是反映其当前状态,就像string.Length或任何其他类似的属性一样。

1
字符串是不可变的,因此它们的长度无法更改。一个更好的例子是 List<T>,如果在调用之间添加或删除了元素,则其 Count 属性可以更改。 - Gabe
字符串是不可变的,但这并不意味着它的引用也是不可变的。这就是为什么我说“如果其他东西在外部更改了字符串”,而不是说“更改了长度”。但你说得对。 - Erik Funkenbusch
用 StringBuilder 替换 String,这样特定实例的长度确实是可变的。顺便说一下,我认为 StringBuilder.Length 应该是只读属性,并且应该有 Truncate、Pad 和 SetLength 方法;后两个方法有一个可选参数来指定填充字符。 - supercat

2
在决定“方法”与“属性”时,一个建议的测试是:“连续调用会返回不同的结果吗”。我认为更好的测试是类似但不完全相同的问题:“对同一或不同的例程进行调用是否会影响未来调用的结果?”在大多数情况下,两个问题的答案将是相同的,因为后续调用例程产生不同结果的最常见原因是,前一个调用导致后面的调用返回比本来应该返回的结果不同。
在DateTime.Now的情况下,一个调用如何影响另一个返回值的唯一方式是,第一个调用所花费的执行时间导致第二个调用发生比原本晚得多。虽然一些人可能认为时间流逝是第一个调用的状态更改副作用,但我认为有许多属性比DateTime.Now执行时间更长,因此对其中任何一个调用都有很大可能性会改变后续DateTime.Now调用返回的值。
请注意,如果“获取时间”的例程是一个虚拟类成员而不是静态成员,那么这将有利于使它成为一个方法;虽然“预期”的实现不会影响任何对象的状态,但可能(或至少是可能的)有一些实现具有副作用。例如,在RemoteTimeServer对象上调用Now可能会尝试从远程服务器获取时间,并且这样的尝试可能对系统的其余部分产生相当大的副作用(例如,通过导致一个或多个机器缓存DNS/IP路由信息,使得下一次访问相同服务器的尝试将比原来快100毫秒)。

0

由于在何时使用方法和属性上没有明确的规则,DateTime.Now实际上只是读取服务器状态的公开属性,它可能会不断变化,但DateTime.Now永远不会影响任何属性、对象或其他状态,因此它是框架中的一个属性。


1
除非你考虑海森堡原理,否则仅仅观察某物就会改变它的状态 ;) - Erik Funkenbusch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接