Last active
December 20, 2015 08:39
-
-
Save Porges/6102529 to your computer and use it in GitHub Desktop.
A property type for people who are scared of Observables
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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