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

时间:2015-05-26 22:17:35

标签: 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

然而,可变属性不是引用,我似乎无法找到任何方法来设计这样一个可编译的东西。例如,

let setIfChanged oldVal newVal = if newVal <> oldVal then oldVal <- newVal 

只会告诉我oldVal不可变。

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

2 个答案:

答案 0 :(得分:5)

我认为没有任何简单的方法可以“免费”执行此操作。

可能使这一点变得更容易的一个技巧是,您可以将属性的getter和setter视为函数,因此您可以将setIfChanged编写为具有两个函数的函数:

let setIfChanged getter setter v = 
  if getter() <> v then setter(v)

鉴于具有专家A的课程P,您可以使用set_Pget_P作为参数进行调用:

let a = A()
setIfChanged a.get_P a.set_P 0
setIfChanged a.get_P a.set_P 1

编译器允许您访问get_Pset_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,只有在值实际发生变化时才会看到“更改值”。

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