F# 元组常量从不初始化

F# Tuple Constant never initializes

我已经这样声明了一个元组:

module MyModule =
  let private INVALID_TUPLE = ("0", DateTime.MinValue)

当我在模块中较低的位置引用它时,它始终为 null:

let private invalidForNone someOtherTuple =
  match someOtherTuple with
  | None -> INVALID_TUPLE  // it's null
  | Some(t) -> t

此外,当我在元组声明上放置断点时,它永远不会命中。

如果我在脚本 (fsx) 文件中执行完全相同的操作,开始调试,执行,元组声明上的断点命中并且对元组的引用是正确的。

我的模块的 ILSpy 显示生成了一些启动代码,其中包含创建 INVALID_TUPLE 的 Main 方法。显然,出于某种原因,这不是 运行?

这是一个重现该行为的示例(现在我意识到它与 MSTest 执行代码的方式有关)。从 C# 单元测试中调用它;结果将为空。事实上,F# 代码中的断点根本不会执行。

module NullTupleTest
open System

let private INVALID_TUPLE = ("invalid", DateTime.MinValue)

let private TupleTest someTuple =
  match someTuple with
  | None -> INVALID_TUPLE
  | Some(dt) -> dt

let Main = TupleTest None

当您 运行 代码编译为可执行文件的方式未 运行 已编译可执行文件的 Main 方法时,可能会发生错误 - 例如通过库或使用单元测试 运行ner。解决方案是将 F# 项目编译为库,并可能将另一个可执行文件作为入口点。 (或者,您也可以修改代码以避免 let 绑定全局值,但我更喜欢第一种方法。)

这是因为 F# 编译器对编译为可执行文件的代码和编译为库的代码的初始化处理方式不同。

  • 对于库,初始化代码放在静态构造函数中,在第一次访问字段时执行
  • 对于可执行文件,初始化代码放在 Main 方法中,并在应用程序启动时 运行s(但仅当它作为普通可执行文件启动时)。

我认为这是因为 F# 编译器试图保持初始化发生的顺序(从上到下)。对于可执行文件,这可以通过 Main 方法中的 运行ning 初始化程序来完成。对于库,没有可靠的方法来做到这一点(因为库没有 "initialization"),因此使用静态构造函数是下一个最佳选择。