Skip to content

Instantly share code, notes, and snippets.

@gsoulavy
Last active March 14, 2024 03:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gsoulavy/b3b7fbce1439d470535806e72419e0a9 to your computer and use it in GitHub Desktop.
Save gsoulavy/b3b7fbce1439d470535806e72419e0a9 to your computer and use it in GitHub Desktop.
INotifyPropertyChanged implemented via Castle DynamicProxy
public class Program
{
private string lastPropertyToChange;
void Main()
{
var model = new AutoNotifyPropertyChangedProxyCreator().Create<Person>();
model.PropertyChanged += (o, e) => lastPropertyToChange = e.PropertyName;
model.Name = "Harold";
model.Name = "James";
}
}
public interface IAutoNotifyPropertyChanged : INotifyPropertyChanged
{
bool IsDirty { get; set; }
void OnPropertyChanged(string methodName);
}
public class BaseModel : IAutoNotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual bool IsDirty { get; set; }
public void OnPropertyChanged([CallerMemberName]string methodName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(methodName));
}
}
public class Person : BaseModel
{
public virtual string Name { get; set; }
public virtual string PostCode { get; set; }
}
public class AutoNotifyPropertyChangeInterceptor<TModel> : IInterceptor where TModel : IAutoNotifyPropertyChanged
{
private const string SetPrefix = "set_";
private const string GetPrefix = "get_";
public void Intercept(IInvocation invocation)
{
try
{
var property = GetProperty(invocation);
if (!invocation.Method.Name.StartsWith(GetPrefix))
{
var oldvalue = property.GetValue(invocation.InvocationTarget, null);
Console.WriteLine($"Old: {property.Name}: {oldvalue}");
}
invocation.Proceed();
if (!invocation.Method.Name.StartsWith(SetPrefix)) return;
if (!(invocation.Proxy is IAutoNotifyPropertyChanged)) return;
var methodInfo = invocation.Method;
var model = (IAutoNotifyPropertyChanged)invocation.Proxy;
var value = property.GetValue(invocation.InvocationTarget);
Console.WriteLine($"New: {property.Name}: {value}");
model.OnPropertyChanged(methodInfo.Name.Substring(SetPrefix.Length));
ChangeNotificationForDependentProperties(methodInfo, model);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
public PropertyInfo GetProperty(IInvocation invocation)
{
var type = invocation.InvocationTarget.GetType();
return type.GetProperty(invocation.Method.Name.Substring(SetPrefix.Length));
}
private void ChangeNotificationForDependentProperties(MethodInfo methodInfo, IAutoNotifyPropertyChanged model)
{
if (NoAdditionalProperties(methodInfo)) return;
string[] properties = GetAdditionalPropertiesToChangeNotify(methodInfo);
foreach (string propertyName in properties)
model.OnPropertyChanged(propertyName);
}
private bool NoAdditionalProperties(MethodInfo methodInfo)
{
var pi = GetPropertyInfoForSetterMethod(methodInfo);
return (pi == null || pi.GetAttribute<NotifyChangeForAttribute>() == null);
}
private string[] GetAdditionalPropertiesToChangeNotify(MethodInfo methodInfo)
{
var pi = GetPropertyInfoForSetterMethod(methodInfo);
var attribute = pi.GetAttribute<NotifyChangeForAttribute>();
return attribute.NotifyChangeFor;
}
private PropertyInfo GetPropertyInfoForSetterMethod(MethodInfo methodInfo)
{
var propertyName = methodInfo.Name.Substring(SetPrefix.Length);
return methodInfo.DeclaringType.GetProperty(propertyName);
}
}
public class AutoNotifyPropertyChangedProxyCreator
{
public TModel Create<TModel>(params object[] constructorParameters) where TModel : IAutoNotifyPropertyChanged
{
var interceptor = new AutoNotifyPropertyChangeInterceptor<TModel>();
var proxyGenerator = new ProxyGenerator(new PersistentProxyBuilder());
return (TModel)proxyGenerator.CreateClassProxy(typeof(TModel), constructorParameters, interceptor);
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class DoNotNotifyChangesAttribute : Attribute
{
}
public class NotifyChangeForAttribute : Attribute
{
public string[] NotifyChangeFor { get; private set; }
public NotifyChangeForAttribute(params string[] notifyChangeFor)
{
NotifyChangeFor = notifyChangeFor;
}
}
public static class AttributeHelperExtensions
{
public static A GetAttribute<A>(this MemberInfo memberInfo) where A : Attribute
{
return GetAttribute<A>(memberInfo, true);
}
public static A GetAttribute<A>(this MemberInfo memberInfo, bool inherit) where A : Attribute
{
var atts = GetAttributes<A>(memberInfo, inherit);
if (atts == null || atts.Length == 0) return null;
return atts[0];
}
public static A[] GetAttributes<A>(this MemberInfo memberInfo) where A : Attribute
{
return GetAttributes<A>(memberInfo, true);
}
public static A[] GetAttributes<A>(this MemberInfo memberInfo, bool inherit) where A : Attribute
{
var atts = memberInfo.GetCustomAttributes(typeof(A), inherit);
if (atts == null) return null;
return atts.Cast<A>().ToArray();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment