Created
May 31, 2019 14:07
-
-
Save fdbeirao/2b07ac4994d214d9bb35089a83532434 to your computer and use it in GitHub Desktop.
C# IObservableProperty
This file contains hidden or 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; | |
namespace ObservableProperty | |
{ | |
public interface IObservableProperty<T> : IObservable<T> | |
{ | |
T Value { get; } | |
} | |
} |
This file contains hidden or 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.Reactive.Subjects; | |
namespace ObservableProperty | |
{ | |
/// <summary> | |
/// ObservableProperty is an immutable data structure which holds the last/current value of a property, | |
/// as well as providing you with an observable stream of changes to this property. The property observers | |
/// will be carried over even with "mutations" of this immutable data structure. | |
/// </summary> | |
public sealed class ObservableProperty<T> : IObservableProperty<T>, IObservable<T>, IDisposable | |
{ | |
/// <summary> | |
/// Provides a new instance of the ObservableProperty. | |
/// </summary> | |
public static ObservableProperty<T> New(T initialValue) => | |
new ObservableProperty<T>(initialValue, new BehaviorSubject<T>(initialValue)); | |
/// <summary> | |
/// This is how you "mutate" this observable property. Keep in mind that you receive a new instance of | |
/// this immutable data structure, so you have to hold it. Any subscribers of notifications will be kept | |
/// and notified of this modification. | |
/// </summary> | |
public ObservableProperty<T> WithValue(T newValue) | |
{ | |
var updatedProperty = new ObservableProperty<T>(newValue, _stream); | |
updatedProperty._stream.OnNext(newValue); | |
return updatedProperty; | |
} | |
private ObservableProperty(T value, ISubject<T> stream) => | |
(Value, _stream) = | |
(value, @stream); | |
/// <summary> | |
/// The last/current value assigned to this property. | |
/// </summary> | |
public T Value { get; } | |
/// <summary> | |
/// Subscribe to future modifications of this property. Upon subscription you will also receive the | |
/// current value of the property. As per the IObservable<T> pattern, dispose of the returned object | |
/// to cancel this subscription. | |
/// </summary> | |
public IDisposable Subscribe(IObserver<T> observer) => | |
_stream.Subscribe(observer); | |
private readonly ISubject<T> _stream; | |
#region IDisposable Support | |
private bool hasBeenDisposed = false; // To detect redundant calls | |
public void Dispose() => | |
Dispose(true); | |
private void Dispose(bool disposing) | |
{ | |
if (!hasBeenDisposed) | |
{ | |
if (disposing) | |
{ | |
_stream.OnCompleted(); | |
} | |
hasBeenDisposed = true; | |
} | |
} | |
#endregion | |
} | |
/// <summary> | |
/// ObservableProperty is an immutable data structure which holds the last/current value of a property, | |
/// as well as providing you with an observable stream of changes to this property. The property observers | |
/// will be carried over even with "mutations" of this immutable data structure. | |
/// </summary> | |
public static class ObservableProperty | |
{ | |
public static ObservableProperty<T> New<T>(T initialValue) => | |
ObservableProperty<T>.New(initialValue); | |
} | |
} |
This file contains hidden or 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 ObservableProperty; | |
using System; | |
using System.Reactive.Linq; | |
using System.Threading.Tasks; | |
using Xunit; | |
namespace ObservablePropertyTests | |
{ | |
public class Tests | |
{ | |
public class User | |
{ | |
private readonly ObservableProperty<string> _name; | |
public string Id { get; } | |
public string Name => _name.Value; | |
public IObservable<string> NameStream => _name; | |
private User(string id, ObservableProperty<string> name) => | |
(Id, _name) = | |
(id, name); | |
public static User New(string id, string name) => | |
new User(id, ObservableProperty<string>.New(name)); | |
public User WithName(string name) => | |
new User(Id, _name.WithValue(name)); | |
} | |
private static string SomeRandomString => | |
Guid.NewGuid().ToString(); | |
[Fact] | |
public async Task OnCreation_Then_Subscription_WeGetCurrentValue() | |
{ | |
var name = SomeRandomString; | |
var user1 = User.New(id: SomeRandomString, name); | |
Assert.Equal(name, user1.Name); | |
var nameInStream = await user1.NameStream.Take(1); | |
Assert.Equal(name, nameInStream); | |
} | |
[Fact] | |
public async Task OnModification_Subscription_CarriesOver() | |
{ | |
var name = SomeRandomString; | |
var user1 = User.New(id: SomeRandomString, name); | |
var initialNameInStream = await user1.NameStream.Take(1); | |
Assert.Equal(name, initialNameInStream); | |
var user2 = user1.WithName("new name"); | |
var newUserNameInStream = await user1.NameStream.Take(1); | |
Assert.Equal("new name", newUserNameInStream); | |
} | |
[Fact] | |
public async Task Subscription_HasOnlyMostRecentValue() | |
{ | |
var user1 = User.New(id: SomeRandomString, SomeRandomString); | |
var newUser = user1.WithName("new name"); | |
var newUserNameInStream = await user1.NameStream.Take(1); | |
Assert.Equal("new name", newUserNameInStream); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I still have mixed feelings about this implementation. The fact that
ObservableProperty
is an immutable data structure signals some safety guarantees, but then it has an observable side-effect on execution (invokingonNext
on the IObservable).. maybe this could be useful for someone, but use it with a grain of salt (like all code).Cheers!