无法更新 struct 中的可变字段?

Can't Update Mutable Field in Struct?

提问人:EricP 提问时间:11/8/2018 更新时间:11/8/2018 访问量:398

问:

谁能告诉我为什么这个 Counter 结构不起作用?它始终在调用 Incr 之间将值重置为 0。

type Counter = 
    struct

        val mutable i: int 

        member public this.Incr() =
            this.i <- this.i + 1

        member public this.Count = 
            this.i
    end

let noCounty(s:string): int = 
    let x = new Counter()
    x.Incr()
    x.Incr()
    x.Count
结构 f# 可变

评论

0赞 s952163 11/8/2018
别这样。:-)我的意思是不要在结构内部发生变异。如果你愿意,你可以通过显式制作 x .mutable
1赞 EricP 11/8/2018
我使计数器int可变?这是一个简化的代码片段,显示了我的问题。出于性能原因,我需要这样做。当我迭代数百万个 3D 点时,我正在更新字段。我不想将 ints 和 floats 框入 ref 类型。
0赞 s952163 11/8/2018
是的,当然。 是一个值类型,由值传递,因此您需要使保存结构本身的值可变,因为当您更改计数器时,您也会更改整个结构。struct

答:

1赞 AMieres 11/8/2018 #1

我不知道为什么它不能像你那样工作,但它是这样工作的:

type Counter() = 
    [<DefaultValue>]
    val mutable i: int

    member public this.Incr() =
        this.i <- this.i + 1

    member public this.Count = 
        this.i

评论

1赞 s952163 11/8/2018
不过,这略有不同,这不是一个.classstruct
2赞 Tomas Petricek 11/8/2018 #2

可变结构的语义总是令人困惑,因为在某些情况下,当您以某些方式使用结构时,结构会被意外复制 - 因此最好避免在结构中发生突变。

您可以通过将变量标记为可变来使此工作。鉴于您当前对 的定义,以下内容按预期工作:xnoCountyCounter

let noCounty() = 
    let mutable x = new Counter()
    x.Incr()
    x.Incr()
    x.Count

我同意这很令人困惑。我认为逻辑是,如果您将变量定义为不可变的,那么编译器会将结构体的值复制到新变量中,然后再进行任何可能改变它的调用。因此,编译后的代码看起来更像是:

let noCounty () = 
    let x = new Counter()
    (let t1 = x in t1.Incr())
    (let t2 = x in t2.Incr())
    (let t3 = x in t3.Count)

我希望编译器能给我一些警告 - 所以也许在这种情况下缺乏警告应该报告为编译器错误。(尽管这种行为可能是有意为之。

评论

1赞 EricP 11/8/2018
我以为我做错了什么,但 ILSpy 证实了这一点。如果它看到任何可变成员(甚至是内部成员),它会静默克隆该变量。我可能不得不使用 F# 中止。我将使用用 c# 编写的商业库,并且不能让方法调用触发大型 3D 网格的静默复制。
1赞 Tomas Petricek 11/8/2018
@EricP 如果将变量标记为可变变量,则不会出现静默重复。此外,如果你有大型的 3D 网格,那么它们可能存储在一个数组中(或类似的东西),所以它们无论如何都会被堆分配(即使存储引用的结构是复制的,网格本身也不会......
1赞 CaringDev 11/8/2018
如果将警告级别设置为 5 或激活 warnon 52,编译器将警告您防御性复制。
0赞 EricP 11/9/2018
@TomasPetricek 谢谢。F# 不可变现在不那么神秘了。我担心数组是不可变的,但没有引用类型是不可变的。我将 Counter 定义更改为一个类,调用方不必指定可变即可工作。这就提出了一个问题,为什么所有关于F#的不可变性和线程安全优势的讨论。.NET 默认参数由 val 传递。我把它看作是一组毫无意义的 val 类型的局部规则,以及一个有趣的 OO 模式,即其他地方的只读、链接对象。
0赞 Tomas Petricek 11/9/2018
@EricP 这是一个需要更长解释的话题:-)。有两个概念 - 不可变对象和不可变变量。不可变对象比不可变变量重要得多。
0赞 s952163 11/8/2018 #3

如果你真的想这样做,你可以:

[<Struct>]
type Counter = 
        val mutable i: int 

        member public this.Incr() =
            this.i <- this.i + 1

        member public this.Count = 
            this.i

let  mutable x = Counter() // You need to make the struct itself mutable
x.Incr()
x.Incr()
x.Count
//val it : int = 2

老实说,编译器应该抱怨这种行为。虽然在一般情况下,特别是在结构中变异不是一个好主意;你可以,但你需要使它本身可变,因为你正在改变结构本身,它是一种值类型。另一个答案给你一个类,它是一个引用类型。x