Skip to content

Instantly share code, notes, and snippets.

@johnnyelwailer
Created September 13, 2011 11:12
Show Gist options
  • Save johnnyelwailer/1213610 to your computer and use it in GitHub Desktop.
Save johnnyelwailer/1213610 to your computer and use it in GitHub Desktop.
prototype implementation of DependencyObservation.
/// <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&lt;T, TContextProp&gt;.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