Iftm.ComputedProperties 1.0.11

dotnet add package Iftm.ComputedProperties --version 1.0.11
                    
NuGet\Install-Package Iftm.ComputedProperties -Version 1.0.11
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Iftm.ComputedProperties" Version="1.0.11" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Iftm.ComputedProperties" Version="1.0.11" />
                    
Directory.Packages.props
<PackageReference Include="Iftm.ComputedProperties" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Iftm.ComputedProperties --version 1.0.11
                    
#r "nuget: Iftm.ComputedProperties, 1.0.11"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Iftm.ComputedProperties@1.0.11
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Iftm.ComputedProperties&version=1.0.11
                    
Install as a Cake Addin
#tool nuget:?package=Iftm.ComputedProperties&version=1.0.11
                    
Install as a Cake Tool

Iftm.ComputedProperties

This project enables automagic firing of PropertyChanged events. It's perfect for view models.

NuGet

The binaries from this repository are available as the Iftm.ComputedProperties NuGet packge.

Example

Say you have a property A, and a property B that is evaluated as A + 5. To have the PropertyChanged event fired for both A and B whenever A changes, simply inherit from WithComputedProperties like this:

using Iftm.ComputedProperties;

class Demo : WithComputedProperties
{
    private int _a; // value of A

    public int A {
        get => _a;
        set => SetProperty(ref _a, value); 
    }

    // Describe our computation for B using an expression:
    private static readonly ComputedProperty<Demo, int> _b =
        Computed((Demo obj) => obj.A + 5);

    // Implement a property that evaluates _b:
    public int B => _b.Eval(this);
}

The magic of this is that whenever A changes, the PropertyChanged event will be fired for both A and B.

This also works for property chains like a.B.C.D where a, B and C are objects, the property computation is a conditional expressions, etc.

Lifetime

In case object A has properties that depend on object B, the whenever anybody is subscribed to A's PropertyChanged event A will also subscribe to B's PropertyChanged. When A has no more listeners it will disconnect from B.

You can (but don't have to) also call Dispose() on the model above which will disconnect it from all the input events and clear its PropertyChanged event. Frameworks like WPF use the weak event pattern to disconnect from events but explicitly disposing the model should yield better performance and provide for better debuggability.

Expensive Properties

In case the computation of a property is expensive (perhaps it allocates?), it makes sense to cache the value of the property computation and use it in subsequent evaluations if the property has not been changed.

To do this simply inherit your model from WithCachedProperties, declare the variable that will contain the cached value if any, and use it in the call to Eval:

using Iftm.ComputedProperties;

class Demo : WithCachedProperties {
    private string _a;

    public string A {
        get => _a;
        set => SetProperty(ref _a, value); 
    }

    private static readonly ComputedProperty<Demo, string> _b =
        Computed((Demo obj) => obj.A + " blah blah.");

    private string _lastB;

    public string B => _b.Eval(this, ref _lastB);
}

Tasks

In case the value of a property depends on an asynchronous I/O or computation, we provide the TaskModel class that wraps a function CancellationToken → ValueTask<T>. When PropertyChanged listeners are attached to objects of this class, it calls the provided function to get the property value. If all listeners are detached before the task completes, it cancels the call and then repeats it again if listeners are added later.

The two properties TaskModel<T> are Value, which returns the value of the task if it's been completed, and HasValue which tells us whether the task has been completed. HasValue is useful for displaying a progress indication and for knowing whether Value can be successfully read.

Here is an example:

using Iftm.ComputedProperties;

class Demo : WithCachedProperties {
    private int _a;

    public int A {
        get => _a;
        set => SetProperty(ref _a, value);
    }

    private async static ValueTask<int> AsyncFunction(int num, CancellationToken ct) {
        await Task.Delay(2_000, ct);
        return num + 1;
    }

    private ComputedProperty<Demo, TaskModel<int>> _b =>
        Computed((Demo obj) => TaskModel.Create(obj.A, AsyncFunction));

    private TaskModel<int> _lastB;

    public TaskModel<int> B => _b.Eval(this, ref _lastB);
}

Please note that the computation of _b reads the value of the property A and passes it to the AsyncFunction as an argument. This is necessary as the AsyncFunction will execute asynchronously so it would be impossible to determine the dependency chain if it read the properties of our object directly.

Performance

The Iftm.ComputedProperties package is ridiculously efficient. Although it tracks property dependencies at runtime (because in case of A.B.C the object A needs to know when B changes so that it can unsubscribe from the old B and subscribe to the new one), the memory usage is miniscule and cache-friendly, and the CPU usage is negligible. If you are binding to these objects in any UI framework like UWP or WPF you will not feel it and your code will definitely be more robust and simpler.

Iftm.ComputedProperties blows frameworks like ReactiveUI out of the water both in performance and simplicity. Everything is stored inside the single object instead of spawning tens of Observables in order to do simple computations.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 is compatible.  net471 is compatible.  net472 is compatible.  net48 is compatible.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.11 864 11/6/2020
1.0.10 630 4/13/2020
1.0.9 655 1/17/2020
1.0.8 704 10/28/2019
1.0.7 625 10/28/2019
1.0.6 646 10/28/2019
1.0.5 692 10/27/2019
1.0.4 663 10/25/2019
1.0.3 671 10/25/2019
1.0.2 676 10/25/2019
1.0.1 662 10/17/2019
1.0.0 666 10/8/2019