F# struct 成员引用 'this' 导致错误

F# struct member referencing 'this' results in an error

F# 开发新手,C# 开发老手。作为学习 F# 的练习,我正在学习 Eric Lippert 的图形着色系列,将他的 C# 翻译成 F#。我目前正在 part two.

原始 C# 在博客中 post - 这是到目前为止的 F# 翻译 - 但它无法编译:

type BitSet = struct
        val bits : int
        private new(b) = { bits = b }

        static member Empty = BitSet(0)

        member this.Contains (item:int) = (this.bits &&& (1<<< item)) <> 0
        member this.Add (item:int) = BitSet(this.bits ||| (1 <<< item))
        member this.Remove (item:int) = BitSet(this.bits &&& ~~~(1<<<item))
        member this.Bits = seq {
                for item in 0..31 do
                    if this.Contains(item) then
                        yield item
            }
    end

这会根据 Bits:seq 的定义产生非常神秘的错误 "error FS0406: The byref-typed variable 'this' is used in an invalid way. Byrefs cannot be captured by closures or passed to inner functions"。

奇怪的是,将关键字 "struct" 更改为 "class" 会生成有效代码。从 C# 的角度来看,这似乎是无稽之谈,但我确信它背后有一个正当的理由。问题是 - 应该 如何编写 Bits 函数?我需要了解什么基础 F# 原则才能使它有意义?

我认为问题在于 this 引用是作为对结构当前值的引用创建的,因此您可以修改结构(如果需要并且结构是可变的)。

这会导致 seq { .. } 内部出现问题,因为这会在另一个 class 内部生成代码,因此编译器无法将引用传递给 "this" 实例。

如果将 this 值赋给一个普通的局部变量,那么代码可以正常工作:

member this.Bits = 
    let self = this
    seq {
        for item in 0..31 do
            if self.Contains(item) then
                yield item
     }

作为风格问题 - 我可能会使用隐式构造函数,这会使代码更短一些。我也更喜欢 Struct 属性而不是显式 struct .. end 语法,但这两者都只是风格问题(我相信其他人会有不同的偏好)。您可能会发现它很有用,只是作为替代品或用于比较:

[<Struct>]
type BitSet private (bits:int) = 
    static member Empty = BitSet(0)
    member this.Contains (item:int) = (bits &&& (1<<< item)) <> 0
    member this.Add (item:int) = BitSet(bits ||| (1 <<< item))
    member this.Remove (item:int) = BitSet(bits &&& ~~~(1<<<item))
    member this.Bits = 
        let self = this
        seq {
            for item in 0..31 do
                if self.Contains(item) then
                    yield item
        }

this 对于 structclass 意味着不同的东西,因为它们在内存中的表示方式不同。 C# 掩盖了细节并使其正常工作,而 F# 决定您需要自己处理差异。

正确的解决方案是将您关心的值缓存在局部变量中,以避免尝试在 seq 块中使用 this。您可以像 Tomas 显示的那样缓存整个对象,或者只缓存 bits 值并内联 Contains 调用。

在这里解决你的问题:

This produces the very mysterious error "error FS0406: The byref-typed variable 'this' is used in an invalid way. Byrefs cannot be captured by closures or passed to inner functions" from the definition of Bits:seq. Curiously, changing the keyword "struct" to "class" results in valid code. Coming from a C# perspective, this seems like nonsense

从 C# 的角度来看,这应该不是胡说八道。这里:

struct Mutable
{
  public int x;
  public void DoIt()
  {
    Action a = () => {
      this.x = 123;
    };
  }
}

您将在该程序中遇到相同的错误,并得到有用的建议,您可以通过将 "this" 复制到本地来按值捕获它。

这是三个事实的结果:首先,结构 S 中的 thisref S 类型,而不是 S,其次,变量不是值,被捕获,第三,.NET 类型系统不允许在长期存储池(也称为 GC 堆)上存储 ref 变量。 Refs 只能放在短期存储池中:堆栈或寄存器。

这三个事实一起意味着您不能以任何方式将 this 存储在结构中,这可能会比激活时间更长,但这正是我们在创建委托时所需要的;代表加入长期池。