【问题标题】:How to get property value from property-getter quotation如何从属性获取器报价中获取属性值
【发布时间】:2017-11-27 08:29:42
【问题描述】:

TL;DR:如何从表单的引用中获取实际的属性值

<@ myInstance.myProperty @>

我正在尝试使用 F# 简化 INotifyPropertyChanged。我不想直接订阅 PropertyChanged,而是想使用一种方法,该方法采用包含我要订阅的属性的代码引用(例如&lt;@ vm.IsChanged @&gt;)和回调(或者只是引用并返回相关属性的可观察值) .例如:

type MyVm() =
  inherit INPCBaseWithObserveMethod()
  ...

let vm = new MyVm()
vm.Observe <@ vm.IsChanged @> (fun isChanged -> ...)

我是代码引用的新手,我正在努力实现Observe 方法。我知道如何从这种表达式中获取属性 name,但不知道 value。这是我目前所拥有的(注意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"

【问题讨论】:

    标签: f# quotations


    【解决方案1】:

    我根据一些实验和this quotation eval function 弄清楚了。在最简单的情况下(当&lt;@ vm.MyProperty @&gt; 中的vm 是本地let 绑定值时),实例表达式将匹配Value 模式:

    | PropertyGet(Some (Value (instance, _)), propInfo, [])
    

    instance 然后可以传递给PropertyInfo.GetValue。但是,如果 vm 是一个字段(类级别 let 绑定)或其他任何内容,则模式将有所不同(例如,包含嵌套的 FieldGet 需要对其进行评估以获得您可以传递的正确实例到PropertyInfo.GetValue)。

    简而言之,似乎最好的做法是简单地使用我链接到的eval 函数。然后整个ViewModelBase 类变为(参见this snippet 以获得更完整的实现):

    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 而不是直接订阅。

    请注意,在许多情况下,Observable.DistinctUntilChanged 可能需要在Observable.map 之后。 (我使用Observe 来触发视图模型属性中的动画,以及在我的特定动画代码中的适当假设,当随后对回调进行多次调用且属性值未更改时,动画变得非常不稳定。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-09
      • 1970-01-01
      相关资源
      最近更新 更多