如何从property-getter报价中获取属性值

时间:2017-11-27 08:29:43

标签: f# quotations

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"

1 个答案:

答案 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来触发视图模型属性中的动画,并在我的特定动画代码中进行假设,当有几个后续调用具有未更改的属性值的回调时,动画变得非常难以理解。 )