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

时间:2017-01-03 21:31:43

标签: class struct f# this seq

新的F#开发人员,长期以来的C#开发人员。作为学习F#的练习,我正在通过Eric Lippert关于图形着色的系列工作,从C#转换为F#。我目前正致力于part two

最初的C#出现在博文中 - 这是迄今为止的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

这会产生非常神秘的错误“错误FS0406:byref-typed变量'this'以无效的方式使用.Byrefs不能被闭包捕获或传递给内部函数”来自Bits的定义:seq&lt; int&gt;

奇怪的是,将关键字“struct”更改为“class”会产生有效的代码。从C#的角度来看,这似乎是胡说八道,但我确信它背后有一个合理的原因。问题是 - 应该如何我写Bits函数?为理解这一点,我需要了解的基本F#原则是什么?

3 个答案:

答案 0 :(得分:9)

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

这会导致seq { .. }内部出现问题,因为这会在另一个类中生成代码,因此编译器无法将引用传递给&#34;这个&#34;实例

如果为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
        }

答案 1 :(得分:9)

在此处说明你的观点:

  

这会产生非常神秘的错误&#34;错误FS0406:byref-typed变量&#39; this&#39;以无效的方式使用。 Byrefs不能被闭包捕获或传递给内部函数&#34;从比特的定义:seq。   奇怪的是,更改关键字&#34; struct&#34;到&#34;班&#34;得到有效的代码。从C#的角度来看,这似乎是无意义的

从C#角度来看,这似乎不是废话。这里:

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

你会在该程序中得到同样的错误,以及有用的建议,你可以捕获&#34;这个&#34;按值将其复制到本地。

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

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

答案 2 :(得分:3)

this表示structclass不同,因为它们在内存中的表示方式不同。 C#正在掩盖细节并使其有效,而F#决定你需要自己处理差异。

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