Last active
March 14, 2024 03:02
-
-
Save gsoulavy/b3b7fbce1439d470535806e72419e0a9 to your computer and use it in GitHub Desktop.
INotifyPropertyChanged implemented via Castle DynamicProxy
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
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