TL; DR:如何从表格的引文中获取实际的属性值
<@ myInstance.myProperty @>
我正在尝试使用F#简化INotifyPropertyChanged。而不是直接订阅PropertyChanged,我想使用一个方法,该方法接受包含我想要订阅的属性的代码引用(例如<@ vm.IsChanged @>
)和回调(或者只是引用并返回相关的observable)属性)。例如:
type MyVm() =
inherit INPCBaseWithObserveMethod()
...
let vm = new MyVm()
vm.Observe <@ vm.IsChanged @> (fun isChanged -> ...)
我是代码引用的新手,我正在努力实现Observe
方法。我知道如何从这种表达式获取属性 name ,而不是值。这是我到目前为止(注意propInfo.GetValue
中的占位符):
type ViewModelBase() =
// Start INPC boilerplate
let propertyChanged = new Event<_, _>()
interface INotifyPropertyChanged with
[<CLIEvent>]
member __.PropertyChanged = propertyChanged.Publish
member this.OnPropertyChanged(propertyName : string) =
propertyChanged.Trigger(this, new PropertyChangedEventArgs(propertyName))
// End INPC boilerplate
member this.Observe (query: Expr<'a>) (callback: 'a -> unit) : unit =
match query with
| PropertyGet(instanceExpr, propInfo, _) ->
(this :> INotifyPropertyChanged).PropertyChanged
|> Observable.filter (fun args -> args.PropertyName = propInfo.Name)
|> Observable.map (fun _ -> propInfo.GetValue(TODO) :?> 'a)
|> Observable.add callback
| _ -> failwith "Expression must be a non-static property getter"
答案 0 :(得分:0)
我根据一些实验和this quotation eval function得出结论。在最简单的情况下(当df[['col1','col2','col3','col4']].groupby(['col1','col2']).count().\
reset_index()
中的vm
是本地<@ vm.MyProperty @>
- 绑定值)时,实例表达式将匹配let
模式:
Value
然后可以将 | PropertyGet(Some (Value (instance, _)), propInfo, [])
传递给instance
。但是,如果PropertyInfo.GetValue
是一个字段(类级别vm
绑定)或其他任何内容,那么模式将是不同的(例如,包含嵌套的let
,需要对其进行评估才能获得您可以传递给FieldGet
)的正确实例。
简而言之,似乎最好的做法是使用我链接的PropertyInfo.GetValue
函数。然后整个eval
类变为(请参阅this snippet以获得更完整的实现):
ViewModelBase
type ViewModelBase() =
/// Evaluates an expression. From http://www.fssnip.net/h1
let rec eval = function
| Value (v, _) -> v
| Coerce (e, _) -> eval e
| NewObject (ci, args) -> ci.Invoke (evalAll args)
| NewArray (t, args) ->
let array = Array.CreateInstance (t, args.Length)
args |> List.iteri (fun i arg -> array.SetValue (eval arg, i))
box array
| NewUnionCase (case, args) -> FSharpValue.MakeUnion (case, evalAll args)
| NewRecord (t, args) -> FSharpValue.MakeRecord (t, evalAll args)
| NewTuple args ->
let t = FSharpType.MakeTupleType [| for arg in args -> arg.Type |]
FSharpValue.MakeTuple (evalAll args, t)
| FieldGet (Some (Value (v, _)), fi) -> fi.GetValue v
| PropertyGet (None, pi, args) -> pi.GetValue (null, evalAll args)
| PropertyGet (Some x, pi, args) -> pi.GetValue (eval x, evalAll args)
| Call (None, mi, args) -> mi.Invoke (null, evalAll args)
| Call (Some x, mi, args) -> mi.Invoke (eval x, evalAll args)
| x -> raise <| NotSupportedException(string x)
and evalAll args = [| for arg in args -> eval arg |]
let propertyChanged = new Event<_,_>()
interface INotifyPropertyChanged with
[<CLIEvent>]
member __.PropertyChanged = propertyChanged.Publish
member this.OnPropertyChanged(propertyName : string) =
propertyChanged.Trigger(this, new PropertyChangedEventArgs(propertyName))
/// Given a property-getter quotation, calls the callback with the value of
/// the expression every time INotifyPropertyChanged is raised for this property.
member this.Observe (expr: Expr<'a>) (callback: 'a -> unit) : unit =
match expr with
| PropertyGet (_, propInfo, _) ->
(this :> INotifyPropertyChanged).PropertyChanged
|> Observable.filter (fun args -> args.PropertyName = propInfo.Name)
|> Observable.map (fun _ -> eval expr :?> 'a)
|> Observable.add callback
| _ -> failwith "Expression must be a property getter"
当然可以通过简单的修改来返回一个可观察的而不是直接订阅。
请注意,在Observe
之后的许多情况下可能需要Observable.DistinctUntilChanged
。 (我使用Observable.map
来触发视图模型属性中的动画,并在我的特定动画代码中进行假设,当有几个后续调用具有未更改的属性值的回调时,动画变得非常难以理解。 )