Skip to content

Instantly share code, notes, and snippets.

@Porges
Last active December 20, 2015 08:39
Show Gist options
  • Save Porges/6102529 to your computer and use it in GitHub Desktop.
Save Porges/6102529 to your computer and use it in GitHub Desktop.
A property type for people who are scared of Observables
using System;
using System.Drawing;
using Properties;
namespace Example
{
class Program
{
static void Main(string[] args)
{
new Program().Run();
}
// as inspired by ReactiveUI's examples
public readonly Property<byte> R = new Property<byte>();
public readonly Property<byte> G = new Property<byte>();
public readonly Property<byte> B = new Property<byte>();
// we don't want colour to be written to as that would disconnect it
public readonly ReadOnlyProperty<Color> Colour;
public Program()
{
Colour = Property.Linked(R, G, B, (r, g, b) => Color.FromArgb(r, g, b));
}
void Run()
{
using (Colour.Subscribe(new WriteLineObserver<Color>()))
{
R.Value = 1;
G.Value = 1;
B.Value = 1;
}
}
class WriteLineObserver<T> : IObserver<T>
{
public void OnNext(T value)
{
Console.WriteLine(value.ToString());
}
public void OnError(Exception error)
{
Console.Error.WriteLine(error);
}
public void OnCompleted()
{
Console.WriteLine("done");
}
}
}
}
using System;
using System.Diagnostics.Contracts;
using System.Reactive.Linq;
using System.Reactive.Subjects;
namespace Properties
{
public class Property<T> : IDisposable, ISubject<T,T>
{
private readonly BehaviorSubject<T> _subject;
private IDisposable _subscription;
public Property() : this(default(T))
{
}
public Property(T value)
{
_subject = new BehaviorSubject<T>(value);
}
public static implicit operator T(Property<T> input)
{
var result = default(T);
using (input._subject.Subscribe(x => result = x))
{
return result;
}
}
public virtual T Value
{
get { return this; }
set
{
RemoveSubscription();
_subject.OnNext(value);
}
}
public void LinkTo(IObservable<T> x)
{
RemoveSubscription();
_subscription = x.Subscribe(_subject);
}
private void RemoveSubscription()
{
using (_subscription){}
}
public void Dispose()
{
RemoveSubscription();
}
public IDisposable Subscribe(IObserver<T> observer)
{
return _subject.Subscribe(observer);
}
// Hide these scary methods:
void IObserver<T>.OnCompleted()
{
_subject.OnCompleted();
}
void IObserver<T>.OnError(Exception error)
{
_subject.OnError(error);
}
void IObserver<T>.OnNext(T value)
{
_subject.OnNext(value);
}
}
public static class Property
{
public static Property<T> Linked<T>(IObservable<T> other)
{
var p = new Property<T>();
p.LinkTo(other);
return p;
}
public static Property<T> Linked<T1, T>(IObservable<T1> x, Func<T1, T> projection)
{
var p = new Property<T>();
p.LinkTo(x, projection);
return p;
}
public static Property<T> Linked<T1, T2, T>(IObservable<T1> x, IObservable<T2> y, Func<T1, T2, T> projection)
{
var p = new Property<T>();
p.LinkTo(x, y, projection);
return p;
}
public static Property<T> Linked<T1, T2, T3, T>(IObservable<T1> x, IObservable<T2> y, IObservable<T3> z, Func<T1, T2, T3, T> projection)
{
var p = new Property<T>();
p.LinkTo(x, y, z, projection);
return p;
}
}
public struct ReadOnlyProperty<T> : IObservable<T>
{
private readonly IObservable<T> _inner;
public ReadOnlyProperty(IObservable<T> inner) : this()
{
_inner = inner;
}
public static implicit operator ReadOnlyProperty<T>(Property<T> inner)
{
return new ReadOnlyProperty<T>(inner);
}
[Pure]
public IDisposable Subscribe(IObserver<T> observer)
{
return _inner.Subscribe(observer);
}
}
public static class PropertyExtensions
{
public static void LinkTo<T1, T>(this Property<T> property, IObservable<T1> x, Func<T1, T> projection)
{
property.LinkTo(x.Select(projection));
}
public static void LinkTo<T1, T2, T>(this Property<T> property, IObservable<T1> x, IObservable<T2> y, Func<T1, T2, T> projection)
{
property.LinkTo(Observable.CombineLatest(x, y, projection));
}
public static void LinkTo<T1, T2, T3, T>(this Property<T> property, IObservable<T1> x, IObservable<T2> y, IObservable<T3> z, Func<T1, T2, T3, T> projection)
{
property.LinkTo(Observable.CombineLatest(x, y, z, projection));
}
}
}
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Properties.Tests
{
[TestClass]
public class PropertyTests
{
class Foo
{
public readonly Property<int> Bar = new Property<int>();
}
[TestMethod]
public void ChangingMiddleOfAChainRedirectsDownstreamPropertiesAlso()
{
var obj1 = new Foo();
var obj1alt = new Foo();
var obj2 = new Foo();
var obj3 = new Foo();
obj2.Bar.LinkTo(obj1.Bar);
obj3.Bar.LinkTo(obj2.Bar);
obj1.Bar.Value = 1;
Assert.AreEqual(1, obj1.Bar.Value); // sanity
Assert.AreEqual<int>(obj1.Bar, obj3.Bar); // sanity
obj2.Bar.LinkTo(obj1alt.Bar);
obj1alt.Bar.Value = 2;
Assert.AreEqual(2, obj1alt.Bar); // sanity
Assert.AreEqual<int>(obj1alt.Bar, obj3.Bar); // test
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment