Created
September 13, 2011 11:12
-
-
Save johnnyelwailer/1213610 to your computer and use it in GitHub Desktop.
prototype implementation of DependencyObservation.
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
/// <summary>Provides a way to define bindings and their values in an intuitive way.</summary> | |
public static class DependencyObservation | |
{ | |
/// <summary>Creates a dependency observer of the specified instance.</summary> | |
/// <typeparam name="T">the type of the isntance</typeparam> | |
/// <typeparam name="TContextProp">The type of the context prop.</typeparam> | |
/// <param name="instance">The instance.</param> | |
/// <param name="contextProperty">The context property.</param> | |
/// <returns>the dependency observer</returns> | |
public static DependencyObservation<T, TContextProp> Of<T, TContextProp>(T instance, Expression<Func<T, TContextProp>> contextProperty) where T : ReactiveObject | |
{ | |
return new DependencyObservation<T, TContextProp>(instance, contextProperty); | |
} | |
} | |
/// <summary>Provides a way to define bindings and their values in an intuitive way.</summary> | |
/// <typeparam name="T">The source type</typeparam> | |
/// <typeparam name="TContextProp">The type of the context prop.</typeparam> | |
public class DependencyObservation<T, TContextProp> : IDisposable | |
where T : ReactiveObject | |
{ | |
/// <summary> | |
/// Holds a list of instances with dependent properties that have already been been subscribed to, in order to prevent double subscription. | |
/// </summary> | |
private static readonly Dictionary<int, BoundInstance> boundInstances = new Dictionary<int, BoundInstance>(); | |
/// <summary>the source instance</summary> | |
private readonly T instance; | |
/// <summary>The contexxt property, to which all notifications are raised.</summary> | |
private readonly Expression<Func<T, TContextProp>> contextProperty; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="DependencyObservation{T,TContextProp}"/> class. | |
/// </summary> | |
/// <param name="instance"> | |
/// The instance. | |
/// </param> | |
/// <param name="contextProperty"> | |
/// The context property. | |
/// </param> | |
public DependencyObservation(T instance, Expression<Func<T, TContextProp>> contextProperty) | |
{ | |
this.instance = instance; | |
this.contextProperty = contextProperty; | |
} | |
/// <summary>Follows the specified property and raises notifications on the specified context property.</summary> | |
/// <typeparam name="TProp">The type of the property.</typeparam> | |
/// <param name="property">The property.</param> | |
/// <returns>The current value of the property</returns> | |
public TProp Follow<TProp>(Expression<Func<T, TProp>> property) | |
{ | |
TProp result; | |
// Check if we already subscribed for this property | |
if (MarkProperty(this.instance, property, out result)) | |
{ | |
return result; | |
} | |
this.instance.ObservableForProperty(property).Subscribe(x => this.instance.RaisePropertyChanged(this.contextProperty)); | |
return result; | |
} | |
/// <summary> | |
/// Follows the specified collection and its items and raises notifications on the specified context property.. | |
/// </summary> | |
/// <typeparam name="TItem">The type of the items.</typeparam> | |
/// <param name="property">The property.</param> | |
/// <returns>The current value of the property</returns> | |
public IReactiveCollection<TItem> FollowCollection<TItem>(Expression<Func<T, IReactiveCollection<TItem>>> property) | |
{ | |
IReactiveCollection<TItem> result; | |
// Check if we already subscribed for this collection property | |
if (MarkProperty(this.instance, property, out result)) | |
{ | |
return result; | |
} | |
this.instance.ObservableForProperty(property).Subscribe(x => this.instance.RaisePropertyChanged(this.contextProperty)); | |
// We want all changes also from items that only implement INotifyPropertyChanged, so we merge two observables. | |
Observable.Merge(result.Changed, result.GetItemChanges()).Subscribe(x => this.instance.RaisePropertyChanged(this.contextProperty)); | |
return result; | |
} | |
/// <summary> | |
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. | |
/// </summary> | |
public void Dispose() | |
{ | |
// Only there for expressing the context of the property in using blocks. | |
} | |
/// <summary>Marks the property of the specified instance as used.</summary> | |
/// <typeparam name="TProp">The type of the prop.</typeparam> | |
/// <param name="instance">The instance.</param> | |
/// <param name="property">The property.</param> | |
/// <param name="value">The value.</param> | |
/// <returns>true if the property was already marked</returns> | |
private static bool MarkProperty<TProp>(T instance, Expression<Func<T, TProp>> property, out TProp value) | |
{ | |
var hash = instance.GetHashCode(); | |
if (!boundInstances.ContainsKey(hash)) | |
{ | |
// Add instance as weak reference to not interference with the objects lifetime | |
boundInstances.Add(hash, new BoundInstance(instance)); | |
} | |
// Cleanup bound instances | |
boundInstances.RemoveWhere(o => !o.Value.Reference.IsAlive); | |
var boundInstance = boundInstances[hash]; | |
var propertyName = property.ToString(); | |
var isCached = boundInstance.IsCached(propertyName); | |
value = (TProp)boundInstance.GetPropertyAccessor(propertyName, property)(); | |
return isCached; | |
} | |
/// <summary>A dependent instance with bound properties</summary> | |
private class BoundInstance | |
{ | |
/// <summary>Gets the properties marked as bound with their property accessor</summary> | |
private readonly Dictionary<string, Func<object>> boundProperties = new Dictionary<string, Func<object>>(); | |
/// <summary> | |
/// Initializes a new instance of the <see cref="DependencyObservation<T, TContextProp>.BoundInstance"/> class. | |
/// </summary> | |
/// <param name="instance">The instance.</param> | |
public BoundInstance(T instance) | |
{ | |
this.Reference = instance; | |
} | |
/// <summary>Gets the reference.</summary> | |
/// <value>The reference.</value> | |
public WeakReference<T> Reference { get; private set; } | |
/// <summary>Determines whether the specified property is cached.</summary> | |
/// <param name="propertyName">Name of the property.</param> | |
/// <returns><c>true</c> if the specified property is cached; otherwise, <c>false</c>.</returns> | |
public bool IsCached(string propertyName) | |
{ | |
return this.boundProperties.ContainsKey(propertyName); | |
} | |
/// <summary>Gets the property accessor.</summary> | |
/// <typeparam name="TProp">The type of the prop.</typeparam> | |
/// <param name="propertyName">Name of the property.</param> | |
/// <param name="property">The property.</param> | |
/// <returns>the compiled accessor func</returns> | |
public Func<object> GetPropertyAccessor<TProp>(string propertyName, Expression<Func<T, TProp>> property) | |
{ | |
var instance = this.Reference.Target; | |
if (this.boundProperties.ContainsKey(propertyName)) | |
{ | |
// Get func from bound property cache | |
return this.boundProperties[propertyName]; | |
} | |
// Compile epression and add to bound property list | |
var compiled = property.Compile(); | |
this.boundProperties.Add(propertyName, () => compiled(instance)); | |
return this.boundProperties[propertyName]; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment