F# 制作不必要的 DateTimeOffset 副本

标签 f# datetimeoffset

这里肯定有一个初学者问题,为什么 F# 编译器会制作不必要的 DateTimeOffset 副本,我该如何阻止它?我不记得这是个问题,但也许自从我在 F# 中使用 DateTimeOffset 以来已经太久了:

let now = DateTimeOffset.Now
now.AddDays(30.0).ToString("yyyy-MM-dd")

在第 2 行,编译器抛出一条错误消息,“已复制该值以确保原始值不会被此操作改变,或者因为从成员返回结构时复制是隐式的,并且另一个成员已寻址。”我如何才能捕获 Now 并添加几天?

最佳答案

您已经发现显示了警告,因为它是级别 5 警告集的一部分。但您可能仍然想知道此警告的实际含义。

警告本身已经暗示了这一点。当您在 struct 类型上调用实例方法时,其中包括像 ToString() 这样的虚拟方法,编译器无法确定底层 struct 是否仍然存在不可变的。这是 F# 的关键点,它非常努力地确保您的原始 let 绑定(bind)保持不变。

F# 编译器中有多项优化,可尽量减少执行的防御性复制的数量。但仍有许多情况无法确定某个值不会改变。对于任何虚拟调用都是如此(您可能会争辩说虚拟调用不能从结构中覆盖,但是当前结构的覆盖可以被覆盖,并且可以访问字段,因此,它可以改变它的数据),更一般地说,对于任何实例成员。

如果我获取您的代码并将其传递给 FSI(在设置 warn:5 之后),它会正确报告两个警告:

> let now = DateTimeOffset.Now
now.AddDays(30.0).ToString("yyyy-MM-dd");;

  now.AddDays(30.0).ToString("yyyy-MM-dd");;
  ^^^^^^^^^^^^^^^^^

stdin(3,1): warning FS0052: The value has been copied to ensure the original is not mutated by this operation or because the copy is implicit when returning a struct from a member and another member is then accessed


  now.AddDays(30.0).ToString("yyyy-MM-dd");;
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stdin(3,1): warning FS0052: The value has been copied to ensure the original is not mutated by this operation or because the copy is implicit when returning a struct from a member and another member is then accessed

val now : DateTimeOffset = 16-7-2020 0:21:21 +02:00
val it : string = "2020-08-15"

通常,JIT 可以优化这些,但就像 F# 一样,JIT 也不能总是确定防御副本是否必要。在这种情况下,复制仍然会发生。我已经看到这种行为对于不同的 JIT 是不同的(甚至可以在 x86 和 x64 之间改变相同的 JIT)。

那么如何防止这种复制的发生呢?这并不总是那么容易,如果您不能更改类型的实现,那当然更不容易。有点违反直觉,如果你告诉 F# 你不关心它是否发生了变化,它会停止为你复制 struct:

let mutable now = DateTimeOffset.Now
now <- now.AddDays(30.)
now.ToString("yyyy-MM-dd");;

请注意,对于某些内置类型,如 floatint,此警告不会引发,因为编译器知道这些类型及其实现并知道它们不会变异(所有 BCL 方法都是安全的)。一般来说,不会为这些内容制作防御性副本。

另请注意,它并非特定于 DateTimeOffset,例如,DateTimeGuid 的行为完全相同,几乎任何其他不属于基本类型的 struct

编辑:Tomas 的回答也很有值(value),他解释了为什么在这种情况下实际上需要 AddDays 的副本。但这是结果的中间副本而不是防御副本,在这种情况下最终是同一件事(令人困惑,我知道)。即使结果不需要中间副本,也会引发警告,例如 ToString

关于F# 制作不必要的 DateTimeOffset 副本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62923631/

相关文章:

c# - 为什么不 DateTime.ToString ("R") 和 DateTime.TryParseExact 往返?

c# - 将时区格式的日期时间转换为本地日期时间

.net - 我无法从字符串中打印字符

f# - 是否有可通过 FSharp.Core 4.3.0.0 重新分发的打包运行时?

f# - 定义 F# 函数并避免返回值

postgresql - 如何使用 Dapper.net 从 Postgres 读取 DateTimeOffset?

mysql - ZoneOffset 的 ID 无效

c# - F#/.NET 空实例异常

F# ViewModel 未通过 XAML 中的 BindingContext 实例化

c# - System.DateTime 和 System.DateTimeOffset 之间的区别