F# - 如果改变了则设置属性

5

我有一个类,其中的属性在调用其setter时会触发多个副作用,例如触发更改事件和/或渲染操作。如果只有当属性的值需要更改时才设置该属性,我该如何设计函数?

使用refs,我只需执行以下操作:

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

然而,可变属性不是引用(refs),我似乎找不到任何设计这种编译的方法。例如,
let setIfChanged oldVal newVal = if newVal <> oldVal then oldVal <- newVal 

只会告诉我 oldVal 不可变。

有没有一种方法可以注释 oldVal,使其为可变(可设置)属性?或者有更好的方法吗?


很遗憾(通常情况下,您希望setter聪明一些,以便在没有任何更改时不会产生副作用),您无法更改源代码 - 就我个人而言,我会考虑包装有问题的类型 - Tomas的答案似乎是一个不错的方法来做到这一点。 - Random Dev
2个回答

5

我认为没有任何简单而又好的方法可以“免费”地做到这一点。

一个可能使得这个过程稍微容易一些的技巧是您可以将属性的getter和setter视为函数,因此您可以编写一个名为setIfChanged的函数,该函数接受两个函数作为参数:

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

假设有一个属性为P的类A,你可以使用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的内容。

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,只有在值实际改变时才会看到“更改值”打印。
内部实现是通过反射获取属性的旧值并设置新值来完成的,因此性能特征不是最佳的,但由于你使用它来引发属性更改类型通知,所以这可能不是一个问题。

如何在对象属性中使用它?例如 setIfChanged foo.Text "bar"。此外,我无法控制在外部 C# 库中定义的类和成员。 - TheDarkSaint
1
@dannytoone 啊,那必须针对一个字段进行操作。它不能针对属性进行操作...我没有意识到你无法访问这些类型。 - Reed Copsey
1
@dannytoone 给了你一个替代方案,使用 F# 中的动态支持来“包装”访问,使你能够在过程中执行操作。 - Reed Copsey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接