Search code examples
.netf#reactiveui

Why does ReactiveUI's ToProperty on an ObservableForProperty cause a FailInit in F#


In ReactiveUI 5.2.0 and F# 3.1, the following F# code causes an InvalidOperationException when constructing the object (from a C# WPF app)

The message is "The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized" and it occurs during a read of the property passed into ObservableForProperty (although the default is to skip the initial value).

type MyVM() as this =
inherit ReactiveObject()

let source : obj = obj()

let ofp =            
    this.ObservableForProperty([|"Source"|])
         .Select(fun x -> x.Value)
        .ToProperty(this, (fun y -> y.Result), obj()) // Exception when executing this

member this.Result with get() = ofp.Value
member this.Source with get() = source // Exception here "The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized"

Edited to add: The problem seems to be that the ToProperty is causing ObservableForProperty to query the "Source" property on subscribe, and F# has checks that the constructor has completed before properties are queried.

More info: In ReactiveNotifyPropertyChangedMixin.nestedObservedChanges, the combination of kicker and fillInValue cause the value to be queried before any change is notified via PropertyChanged


Solution

  • From ReactiveUI version 5.4.0, there's a new overload to ObservableForProperty that takes a simple non-chained property to monitor.

    If you add to that the following extension method:

    let toPropName(query : Expr) = 
        match query with
            | PropertyGet(a, b, list) -> b.Name
            | _ -> ""
    
    [<Extension>]
    type ReactiveObjectExtender =
        [<Extension>]
        static member ObservableForProperty<'u, 't when 'u :> ReactiveObject>(this: 'u, expr : Expr<'t>, ?beforeChange, ?skipInitial) =
            let propertyName = toPropName expr
            this.ObservableForProperty<'u, 't>(propertyName, defaultArg beforeChange false, defaultArg skipInitial true)
    

    Then you can observe property changes using the syntax:

    this.ObservableForProperty(<@ this.MyProperty @>)