我有一个具有属性的类,这些属性在调用setter时会触发一些副作用,例如触发更改事件和/或渲染操作。我怎样才能设计一个只在其值需要改变时才设置属性的函数?
使用refs,我会做这样的事情:
let setIfChanged (oldVal: Ref<'T>) (newVal: 'T) = if newVal <> !oldVal then oldVal := newVal
let y = ref 0
setIfChanged y 1
然而,可变属性不是引用,我似乎无法找到任何方法来设计这样一个可编译的东西。例如,
let setIfChanged oldVal newVal = if newVal <> oldVal then oldVal <- newVal
只会告诉我oldVal不可变。
有没有办法注释oldVal以便它是可变的(可设置的)属性?或者有更好的方法吗?
答案 0 :(得分:5)
我认为没有任何简单的方法可以“免费”执行此操作。
可能使这一点变得更容易的一个技巧是,您可以将属性的getter和setter视为函数,因此您可以将setIfChanged
编写为具有两个函数的函数:
let setIfChanged getter setter v =
if getter() <> v then setter(v)
鉴于具有专家A
的课程P
,您可以使用set_P
和get_P
作为参数进行调用:
let a = A()
setIfChanged a.get_P a.set_P 0
setIfChanged a.get_P a.set_P 1
编译器允许您访问get_P
和set_P
的事实是一个鲜为人知的技巧 - 因此对许多人来说可能有点混乱。
为了完整起见,以下是我用于测试的A
的定义:
type A() =
let mutable p = 0
member x.P
with get() = p
and set(v) = printfn "Setting!"; p <- v
更复杂的方法是使用引用和反射,这会更慢,但它会让你写出类似setIfChanged <@ a.P @> 42
的内容。
答案 1 :(得分:4)
你可以这样做:
let setIfChanged (oldVal: 'a byref) (newVal : 'a) = if newVal <> oldVal then oldVal <- newVal
let mutable test = 42
setIfChanged &test 24
话虽如此,鉴于您对目标的解释,我建议您查看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)
您可以致电someObj?TheProperty <- newVal
,只有在值实际发生变化时才会看到“更改值”。
在内部,这可以通过使用反射来获取属性的旧值并设置新属性,因此它不会具有最佳性能特征,但是当您使用它来引发属性更改类型通知时,这是可能不是问题。