我需要在我希望通过利用do
块来完成的模块中进行一些设置。奇怪的是,我的do
区块似乎永远不会被击中。
更奇怪的是,如果我将模块代码加载到fsi中,它确实会受到攻击。这是我的例子:
Main.fs
[<EntryPoint>]
let main args =
printfn "%b" TestNamespace.M.x
0
TestModule.fs
namespace TestNamespace
module M =
do
printfn "In do"
failwith "Error" // this is line 6
let x = true
当我运行已编译的可执行文件时,我得到了
>test.exe
true
为什么不抛出异常?如果我自己在FSI中运行模块,我会得到
In do
System.Exception: Error
at <StartupCode$FSI_0006>.$FSI_0006.main@() in C:\Projects\Personal2\Playground\fsscripts\fsscripts\TestModule.fs:line 6
Stopped due to error
所以它得到了例外。
我在转换中看到do初始化程序被转换为单独的类
namespace \u003CStartupCode\u0024fsscripts\u003E
{
internal static class \u0024Library1
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
[DebuggerNonUserCode]
internal static int init\u0040;
static \u0024Library1()
{
ExtraTopLevelOperators.PrintFormatLine<Unit>((PrintfFormat<Unit, TextWriter, Unit, Unit>) new PrintfFormat<Unit, TextWriter, Unit, Unit, Unit>("In do"));
Operators.FailWith<Unit>("Error");
bool x = M.x;
}
}
}
VS实际模块代码:
namespace TestNamespace
{
[CompilationMapping(SourceConstructFlags.Module)]
public static class M
{
public static bool x
{
[DebuggerNonUserCode] get
{
return true;
}
}
}
}
那么如何确保do块实际执行?
-
编辑,鉴于上面的例子计为一个简单的常量表达式,因此不会产生可观察的初始化,为什么以下也不起作用?
[<EntryPoint>]
let main args =
printfn "%d" (TestNamespace.M.x id 1)
0
namespace TestNamespace
module M =
do
printfn "In do"
failwith "Error"
let x f a = f a
这打印出1
没问题。
在重新阅读Tomas的评论后编辑,因为函数被认为是一个常量表达式。
答案 0 :(得分:5)
有关问题的详细解释,请参阅this previous SO question的答案。重要的一点是:
文件的静态初始化程序在首次访问具有可观察初始化值的值时执行
现在,“可观察的初始化”有点棘手,但简单的常量初始化肯定没有可观察的初始化 - 这就是为什么不执行do
块的原因。您可以欺骗编译器认为有一些必要的操作,例如添加do ()
:
module M =
do
printfn "In do"
failwith "Error" // this is line 6
let x = (do ()); true
答案 1 :(得分:2)
您可以使用类来保持相同的公共接口,从而获得所需的行为:
type M private () =
static do
printfn "In do"
failwith "Error"
static member val x = true
答案 2 :(得分:2)
这是一种干净的方法。
首先,请注意,如果您希望每次调用函数时都运行初始化代码,您可以这样做:
module M =
let init () =
printfn "In init"
let x f a =
init ()
printfn "In x"
f a
因此,如果您只想调用它一次(静态初始化),您只需从两个地方删除()
:
module M =
let init =
printfn "In init"
let x f a =
init
printfn "In x"
f a
好消息是,您已经记录了您的设计,即首先调用init
代码。如果你有几个单独的初始化代码块,很明显你依赖的是哪个依赖(尽管第一个初始化将执行所有这样的块当然)。
<强>更新强>
这是一个版本,也适用于Release版本。不太干净,但差不多:
module Init =
open System.Runtime.CompilerServices
[<MethodImpl(MethodImplOptions.NoInlining)>]
let call init = init
module M =
let init =
printfn "In init"
let x f a =
Init.call init
printfn "In x"
f a
请注意,init
仍然是unit
值,因此Init.call
是一个非内联函数,根本不执行任何操作。因此,函数调用的开销是无效的。
另一种选择,它也有效,但看起来有点奇怪:
module M =
let mutable init =
printfn "In init"
let x f a =
init <- init
printfn "In x"
f a
有人可以改进吗?