F# - 如果更改则设置属性

dan*_*one 5 f# f#-3.0

我有一个具有属性的类,这些属性在调用setter时会触发一些副作用,例如触发的更改事件和/或渲染操作.我怎样才能设计一个只在其值需要改变时才设置属性的函数?

有了refs,我会做这样的事情:

let setIfChanged (oldVal: Ref<'T>) (newVal: 'T) = if newVal <> !oldVal then oldVal := newVal 
let y = ref 0
setIfChanged y 1
Run Code Online (Sandbox Code Playgroud)

但是,可变属性不是引用,我似乎无法找到任何方法来设计将编译的东西.例如,

let setIfChanged oldVal newVal = if newVal <> oldVal then oldVal <- newVal 
Run Code Online (Sandbox Code Playgroud)

只会告诉我oldVal不可变.

有没有办法注释oldVal,以便它是可变(可设置)属性?或者,还有更好的方法?

Tom*_*cek 5

我不认为有任何简单的方法来"免费"这样做.

一个可能使这更容易的技巧是你可以将属性的getter和setter视为函数,这样你就可以编写setIfChanged一个带有两个函数的函数:

let setIfChanged getter setter v = 
  if getter() <> v then setter(v)
Run Code Online (Sandbox Code Playgroud)

给定一个A具有proeperty 的类,P然后可以使用set_Pget_P作为参数调用它:

let a = A()
setIfChanged a.get_P a.set_P 0
setIfChanged a.get_P a.set_P 1
Run Code Online (Sandbox Code Playgroud)

编译器允许您访问的事实get_P并且set_P是一个鲜为人知的技巧 - 因此对于许多人来说可能有点混乱.为了完整起见,这是A我用于测试的定义:

type A() =
  let mutable p = 0
  member x.P 
    with get() = p
    and set(v) = printfn "Setting!"; p <- v
Run Code Online (Sandbox Code Playgroud)

更复杂的方法是使用引用和反射,这会更慢,但它会让你写出类似的东西setIfChanged <@ a.P @> 42.


Ree*_*sey 4

你可以做:

let setIfChanged (oldVal: 'a byref) (newVal : 'a) = if newVal <> oldVal then oldVal <- newVal
let mutable test = 42
setIfChanged &test 24
Run Code Online (Sandbox Code Playgroud)

话虽这么说,鉴于您对目标的解释,我建议您查看Gjallarhorn。它为可变值之上的信号提供支持,并且在值更改时发生的情况方面设计得非常灵活。它可以用作一种底层机制,当情况发生变化时,您可以在该机制上轻松构建信号(如果它尚未通过模块提供您需要的内容View)。


编辑:

根据您的评论,有问题的属性位于第三方程序集中,一种选择是使用 F# 中的动态支持来重写运算符op_DynamicAssignment( ?<-) 以动态分派到方法,但在此过程中发出“通知”。

鉴于:

let (?<-) source property (value : 'a) = 
    let p = source.GetType().GetProperty(property)
    let r = p.GetValue(source, null) :?> 'a
    if (r <> value) then
        printfn "Changing value"
        source.GetType().GetProperty(property).SetValue(source, value, null)
Run Code Online (Sandbox Code Playgroud)

您可以调用someObj?TheProperty <- newVal,并且仅当值实际更改时您才会看到“正在更改值”打印。

在内部,这通过使用反射来获取属性的旧值并设置新值来工作,因此它不会具有最佳的性能特征,但当您使用它来引发属性更改类型通知时,这可能不是一个问题。