Skip to content

Instantly share code, notes, and snippets.

@owen2
Created March 13, 2015 19:38
Show Gist options
  • Save owen2/c0beb60fd3cbeab41336 to your computer and use it in GitHub Desktop.
Save owen2/c0beb60fd3cbeab41336 to your computer and use it in GitHub Desktop.
The closest thing to magic change notification on automatic properties without using proxies or postcompilers.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
namespace ViewModelBase
{
/// <summary>
/// Implementation of INotifyPropertyChanged.
/// </summary>
public abstract class ChangeNotificationObject : INotifyPropertyChanged
{
private IDictionary<string, IField> _valueCache = new Dictionary<string, IField>();
/// <summary>
/// Raises the PropertyChanged event which will trigger the view to sync the changed properties.
/// </summary>
/// <param name="propertyNames">One or more strings representing property names.</param>
protected void NotifyPropertyChanged(params string[] propertyNames)
{
if (PropertyChanged == null) return; // No one is listening
if (propertyNames.Length == 0)
PropertyChanged(this, new PropertyChangedEventArgs("")); //Notify All
foreach (var name in propertyNames)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
/// <summary>
/// Fires the property changed event.
/// </summary>
/// <param name="exprs">One or more Property selector lambdas </param>
protected void NotifyPropertyChanged(params Expression<Func<object>>[] exprs)
{
NotifyPropertyChanged(exprs.Select(selector => GetPropertyName(selector)).ToArray());
}
/// <summary>
/// Notifies view to update all bindings on this object.
/// </summary>
protected void NotifyAllPropertiesChanged()
{
NotifyPropertyChanged();
}
/// <summary>
/// Occurs when a property has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Gets the value for the property.
/// </summary>
/// <typeparam name="T">The property value type.</typeparam>
/// <param name="propertyName">[Auto populated]The name of the property.</param>
/// <returns>The value of the property.</returns>
public T GetValue<T>([CallerMemberName]string propertyName = "")
{
return GetField<T>(propertyName).Value;
}
/// <summary>
/// Updates the property with the new value.
/// </summary>
/// <typeparam name="T">Type of the property</typeparam>
/// <param name="value">The new value</param>
/// <param name="onPropertyChanging">Predicate to run to see if the property should be changed.</param>
/// <param name="onPropertyChanged">Occurs after the property has changed.</param>
/// <param name="propertyName">[Auto populated]The name of the property. Do not specify this property.</param>
/// <returns><c>true</c> if the property's value was updated.</returns>
protected virtual bool SetValue<T>(T value, Predicate<T> onPropertyChanging = null, Action onPropertyChanged = null, [CallerMemberName] string propertyName = "")
{
IField<T> field = GetField<T>(propertyName);
bool changed = false;
if (propertyName != null)
{
if (!EqualityComparer<T>.Default.Equals(value, field.Value))
{
bool shouldContinue = onPropertyChanging == null || onPropertyChanging(value);
if (shouldContinue)
{
field.Value = value;
NotifyPropertyChanged(propertyName);
if (onPropertyChanged != null)
onPropertyChanged();
changed = true;
}
}
}
return changed;
}
private IField<T> GetField<T>(string propertyName)
{
IField<T> field = null;
if (!_valueCache.ContainsKey(propertyName))
{
field = new Field<T>();
_valueCache.Add(propertyName, field);
}
else
{
field = (IField<T>)_valueCache[propertyName];
}
return field;
}
/// <summary>
/// Returns the property name of the given expression.
/// </summary>
protected string GetPropertyName<T>(Expression<Func<T, object>> property)
{
var memberExpression = property.Body as MemberExpression;
if (memberExpression == null)
{
var convert = property.Body as UnaryExpression;
}
return memberExpression.Member.Name;
}
/// <summary>
/// Returns the property name of the given expression.
/// </summary>
protected string GetPropertyName<T, U>(Expression<Func<T, U>> property)
{
var memberExpression = property.Body as MemberExpression;
if (memberExpression == null)
throw new ArgumentException("Use this to select a property on the type " + typeof(T), "property");
return memberExpression.Member.Name;
}
/// <summary>
/// Returns the property name of the given expression.
/// </summary>
protected string GetPropertyName<T>(Expression<Func<T>> property)
{
var memberExpression = property.Body as MemberExpression;
if (memberExpression == null)
{
UnaryExpression ue = property.Body as UnaryExpression;
if (ue != null)
memberExpression = ue.Operand as MemberExpression;
}
if (memberExpression == null)
throw new ArgumentException("property");
return memberExpression.Member.Name;
}
#region Implementation
internal class Field<T> : IField<T>
{
public T Value { get; set; }
public static implicit operator T(Field<T> t)
{
return t.Value;
}
}
internal interface IField
{
}
internal interface IField<T> : IField
{
T Value { get; set; }
}
#endregion Implementation
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment