Git Product home page Git Product logo

smartreactives's Introduction

SmartReactives Join the chat at https://gitter.im/keyboardDrummer/SmartReactives Build status

SmartReactives is an extension to Rx.NET that detects when an expression changes its value and exposes these changes as an IObservable. Detecting changes in expressions is a common problem, related to user interfaces, caching, validation, and much more.

This example demonstrates the basic functionality:

var input = Reactive.Variable(1);
var inputSquared = Reactive.Expression(() => input * input);
inputSquared.Subscribe(getSquare => Console.WriteLine("square = " + getSquare())); //Prints 'square = 1'

input.Value = 2; //Prints 'square = 4'
input.Value = 3; //Prints 'square = 9'

Reactive.Expression returns a ReactiveExpression<T> which implements IObservable<Func<T>> so we can subscribe to it. The function getSquare is simply a shortcut to ReactiveExpression<T>.Evaluate, which evaluates the function you created the reactive expression with, namely () => input * input.

To start using SmartReactives simply add the NuGet package SmartReactives to your project. Also add SmartReactives.PostSharp if you're using PostSharp. Documentation can be found on the Wiki.

If you're looking for something like SmartReactives but outside of .NET then take a look at these projects:

Examples

This section demonstrates the functionality of SmartReactives by showing a number of examples.

Caching

This example shows how to use Reactive.Cache to get a cache which automatically clears itself when it becomes stale.

var input = Reactive.Variable(2); //We define a reactive variable.
Func<int> f = () => //f is the calculation we want to cache.
{
    Console.WriteLine("cache miss");
    return input * input; //f depends on our reactive variable 'input'.
};
var cache = Reactive.Cache(f); //We base our cache on f.

Assert.AreEqual(4, cache.Get()); //Prints 'cache miss'
Assert.AreEqual(4, cache.Get()); //Cache hit.

input.Value = 3; //Cache becomes stale.

Assert.AreEqual(9, cache.Get()); //Prints 'cache miss'
Assert.AreEqual(9, cache.Get()); //Cache hit.

Collections

This example shows off how to make an IList<T> reactive by calling ToReactive on it. The reactive list is precise: if you access an index you will only get an update if that particular index changes.

var reactiveList = new List<int> { 0, 1, 2 }.ToReactive();
var elementAtIndex1 = Reactive.Expression(() => reactiveList[1]);

//Prints 'item at index 2 changed to 1'
elementAtIndex1.Subscribe(getValue => Console.WriteLine("item at index 1 changed to " + getValue())); 

reactiveList[1] = 3; //Prints 'item at index 1 changed to 3'
reactiveList[2] = 4; //Prints nothing
reactiveList.Add(5); //Prints nothing
reactiveList.Insert(0, 6); //Prints 'item at index 1 changed to 0'

Next to IList<T> there are also reactive collections for ISet<T> and IDictionary<T,U>.

Composition

This example shows that a ReactiveExpression can refer to other ReactiveExpressions. In this way you can build arbitrary graphs of reactive objects. The example demonstrates a graph in the shape of a diamond.

var input = Reactive.Variable(1);
var timesTwo = Reactive.Expression(() => input * 2);
var timesThree = Reactive.Expression(() => input * 3);
var sumOfBoth = Reactive.Expression(() => timesTwo.Evaluate() + timesThree.Evaluate());
sumOfBoth.Subscribe(getValue => Console.WriteLine("sumOfBoth = " + getValue())); //Prints 'sumOfBoth = 5'
input.Value = 2; //Prints 'sumOfBoth = 10'
input.Value = 3; //Prints 'sumOfBoth = 15'

Note that although the input has two paths in the graph to sumOfBoth, there is only one notification for sumOfBoth when input changes. SmartReactives makes sure to notify an expression only once when its values changes.

Precise

In the following example, the expression leftOrRight only depends on variable right when variable left is false, since we are using the lazy or operator ||. If we change right while left is false, then we don't get any updates from leftOrRight. In general, SmartReactives won't give you any updates for old dependencies or possible future dependencies.

var left = Reactive.Variable(false);
var right = Reactive.Variable(false);
var leftOrRight = Reactive.Expression(() => left || right);
leftOrRight.Subscribe(getValue => Console.WriteLine("leftOrRight = " + getValue())); //Prints 'leftOrRight = False'

right.Value = true; //Prints 'leftOrRight = True'
left.Value = true; //Prints 'leftOrRight = True'
right.Value = false; //Prints nothing

Note that if we only want to get updates if leftOrRight changes then we can use leftOrRight.DistinctUntilChanged().Subscribe(...).

Properties

This examples demonstrates two methods to implement a reactive property. The first method uses the class ReactiveVariable that we already know as a backing field for our reactive property. The second method applies ReactiveVariableAttribute to the property, which in combination with PostSharp does all the work.

class ReactiveProperties
{
    readonly ReactiveVariable<int> usingABackingField = Reactive.Variable(1);
    int UsingABackingField
    {
        get { return usingABackingField.Value; }
        set { usingABackingField.Value = value; }
    }

    [ReactiveVariable]
    int UsingAnAttributeAndPostSharp { get; set; } = 1;

    public void Test()
    {
        var product = Reactive.Expression(() => UsingABackingField * UsingAnAttributeAndPostSharp);
        product.Subscribe(getProduct => Console.WriteLine("product = " + getProduct())); //Prints 'product = 1'
        UsingAnAttributeAndPostSharp = 2; //Prints 'product = 2'
        UsingABackingField = 2; //Prints 'product = 4'
    }
}

ReactiveCacheAttribute

The following example demonstrates using ReactiveVariableAttribute and ReactiveCacheAttribute to effortlessly setup a cache.

class CachingCalculator
{
    [ReactiveVariable]
    public int Input { get; set; }

    [ReactiveCache]
    public double Square
    {
        get
        {
            Console.WriteLine("cache miss");
            return Math.Pow(Input, 2);
        }
    }

    [Test]
    public static void ReactiveCache()
    {
        var calculator = new CachingCalculator();
        calculator.Input = 2;

        Assert.AreEqual(4, calculator.Square); //Cache miss. Prints 'cache miss'
        Assert.AreEqual(4, calculator.Square); //Cache hit.

        calculator.Input = 3; //Cache becomes stale.

        Assert.AreEqual(9, calculator.Square); //Cache miss. Prints 'cache miss'
        Assert.AreEqual(9, calculator.Square); //Cache hit.
    }
}

NotifyPropertyChanged

Implementing PropertyChanged for a property is a known cause for boilerplate. PostSharp allows you to remove this boilerplate using its attribute NotifyPropertyChanged. However, sometimes a property A depends on another property B. In this case we would like both properties to call PropertyChanged when B changes. The PostSharp attribute NotifyPropertyChanged won't do this, but SmartNotifyPropertyChanged will, as shown in the following example.

class Calculator : HasNotifyPropertyChanged
{
    [SmartNotifyPropertyChanged]
    public int Number { get; set; }

    [SmartNotifyPropertyChanged]
    public int SquareOfNumber => Number * Number;

    public static void SquareDependsOnNumber()
    {
        var calculator = new Calculator();
        calculator.Number = 1;

        Console.WriteLine("square = " + calculator.SquareOfNumber); //Prints 'square = 1'
        calculator.PropertyChanged += (sender, eventArgs) =>
        {
            if (eventArgs.PropertyName == nameof(SquareOfNumber))
                Console.WriteLine("square = " + calculator.SquareOfNumber);
        };

        calculator.Number = 2; //Prints 'square = 4'
        calculator.Number = 3; //Prints 'square = 9'
    }
}

smartreactives's People

Contributors

apfunk avatar keyboarddrummer avatar stanislavromanov avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.