如何从 属性-getter 报价中获取 属性 值
How to get property value from property-getter quotation
TL;DR: 如何从
形式的引用中获取实际的 属性 值
<@ myInstance.myProperty @>
我正在尝试使用 F# 简化 INotifyPropertyChanged。我不想直接订阅 PropertyChanged,而是想使用一种方法,该方法采用包含我要订阅的 属性 的代码引用(例如 <@ vm.IsChanged @>
)和回调(或者只是引用和 returns 相关 属性) 的可观察值。例如:
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"
我根据一些实验和 this quotation eval function 弄明白了。在最简单的情况下(当 <@ vm.MyProperty @>
中的 vm
是本地 let
绑定值时),实例表达式将匹配 Value
模式:
| PropertyGet(Some (Value (instance, _)), propInfo, [])
instance
然后可以传递给 PropertyInfo.GetValue
。但是,如果 vm
是一个字段(class-level let
绑定)或其他任何东西,那么模式将不同(例如,包含一个嵌套的 FieldGet
需要进行评估以获得可以传递给 PropertyInfo.GetValue
) 的正确实例。
简而言之,最好的做法似乎就是使用我链接到的 eval
函数。整个 ViewModelBase
class 就变成了(更完整的实现参见 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
当然可以简单地修改为 return 一个可观察的而不是直接订阅。
请注意,在许多情况下,Observable.map
之后可能需要 Observable.DistinctUntilChanged
。 (除其他外,我使用 Observe
从视图模型属性触发动画,并且在我的特定动画代码中进行了适当的假设,当随后多次调用未更改的回调时,动画变得非常不稳定 属性 值。)
TL;DR: 如何从
形式的引用中获取实际的 属性 值<@ myInstance.myProperty @>
我正在尝试使用 F# 简化 INotifyPropertyChanged。我不想直接订阅 PropertyChanged,而是想使用一种方法,该方法采用包含我要订阅的 属性 的代码引用(例如 <@ vm.IsChanged @>
)和回调(或者只是引用和 returns 相关 属性) 的可观察值。例如:
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"
我根据一些实验和 this quotation eval function 弄明白了。在最简单的情况下(当 <@ vm.MyProperty @>
中的 vm
是本地 let
绑定值时),实例表达式将匹配 Value
模式:
| PropertyGet(Some (Value (instance, _)), propInfo, [])
instance
然后可以传递给 PropertyInfo.GetValue
。但是,如果 vm
是一个字段(class-level let
绑定)或其他任何东西,那么模式将不同(例如,包含一个嵌套的 FieldGet
需要进行评估以获得可以传递给 PropertyInfo.GetValue
) 的正确实例。
简而言之,最好的做法似乎就是使用我链接到的 eval
函数。整个 ViewModelBase
class 就变成了(更完整的实现参见 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
当然可以简单地修改为 return 一个可观察的而不是直接订阅。
请注意,在许多情况下,Observable.map
之后可能需要 Observable.DistinctUntilChanged
。 (除其他外,我使用 Observe
从视图模型属性触发动画,并且在我的特定动画代码中进行了适当的假设,当随后多次调用未更改的回调时,动画变得非常不稳定 属性 值。)