Skip to content

Instantly share code, notes, and snippets.

@fairking
Last active May 15, 2021 10:58
Show Gist options
  • Save fairking/7c07a5af6af49d5da8ccd285020a66ec to your computer and use it in GitHub Desktop.
Save fairking/7c07a5af6af49d5da8ccd285020a66ec to your computer and use it in GitHub Desktop.
ObservableInterceptor
using System;
using System.Collections.Generic;
using System.Reflection;
using Castle.DynamicProxy;
namespace MyTest.Observable
{
internal class ObservableInterceptor : IInterceptor
{
public bool IsChanged { get; protected set; }
private HashSet<string> _changedProperties = new HashSet<string>();
public IReadOnlySet<string> ChangedProperties => _changedProperties;
public void Intercept(IInvocation invocation)
{
if (IsSetter(invocation.Method))
{
var propertyName = invocation.Method.Name.Substring(4);
var property = invocation.TargetType.GetProperty(propertyName);
var oldValue = property.GetValue(invocation.InvocationTarget);
var newValue = invocation.Arguments[0];
if (!object.Equals(oldValue, newValue))
{
IsChanged = true;
_changedProperties.Add(propertyName);
}
}
invocation.Proceed();
}
public void ClearChanges()
{
IsChanged = false;
_changedProperties.Clear();
}
private bool IsSetter(MethodInfo method)
{
return method.IsSpecialName && method.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase);
}
}
public class ObservableProxy
{
private static readonly ProxyGenerator Generator = new ProxyGenerator();
public static T Create<T>() where T : class, new()
{
return Generator.CreateClassProxy<T>(new ObservableInterceptor());
}
public static T Create<T>(T obj) where T : class
{
return Generator.CreateClassProxyWithTarget(obj, new ObservableInterceptor());
}
public static bool IsProxy<T>(T obj) where T : class
{
return ProxyUtil.IsProxy(obj);
}
public static T Unproxy<T>(T obj) where T : class
{
if (!ProxyUtil.IsProxy(obj))
return obj;
const System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
var proxyType = obj.GetType();
var targetField = proxyType.GetField("__target", flags);
var target = targetField.GetValue(obj);
return (T)target;
}
public static bool IsChanged<T>(T obj) where T : class
{
if (!IsProxy(obj))
throw new ArgumentException("Cannot check changes because the object is not a proxy.");
var interceptor = GetInterceptor(obj)
?? throw new ArgumentException("Cannot check changes because the proxy has no ObservableInterceptor implemented.");
return interceptor.IsChanged;
}
public static string[] ChangedProperties<T>(T obj) where T : class
{
if (!IsProxy(obj))
throw new ArgumentException("Cannot check changes because the object is not a proxy.");
var interceptor = GetInterceptor(obj)
?? throw new ArgumentException("Cannot check changes because the proxy has no ObservableInterceptor implemented.");
return interceptor.ChangedProperties.ToArray();
}
public static void ClearChanged<T>(T obj) where T : class
{
if (!IsProxy(obj))
throw new ArgumentException("Cannot check changes because the object is not a proxy.");
var interceptor = GetInterceptor(obj)
?? throw new ArgumentException("Cannot check changes because the proxy has no ObservableInterceptor implemented.");
interceptor.ClearChanges();
}
private static ObservableInterceptor GetInterceptor<T>(T obj) where T : class
{
const System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
var proxyType = obj.GetType();
var interceptorsField = proxyType.GetField("__interceptors", flags);
var interceptor = (interceptorsField.GetValue(obj) as IEnumerable).OfType<ObservableInterceptor>().SingleOrDefault();
if (interceptor == null)
throw new ArgumentException("Cannot check changes because the proxy has no ObservableInterceptor implemented.");
return interceptor;
}
}
public class MyEntity
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
public class ObservableInterceptorTests
{
[Fact]
public void interceptor_is_working()
{
var obj = new MyEntity()
{
Name = "My name",
Description = "My description"
};
var proxy = ObservableProxy.Create(obj);
Assert.Equal("My name", proxy.Name);
Assert.False(ObservableProxy.IsChanged(proxy));
proxy.Name = "My name";
Assert.Equal("My name", proxy.Name);
Assert.False(ObservableProxy.IsChanged(proxy));
proxy.Name = "My name 2";
Assert.Equal("My name 2", proxy.Name);
Assert.True(ObservableProxy.IsChanged(proxy));
Assert.Collection(ObservableProxy.ChangedProperties(proxy), item => Assert.Equal("Name", item));
ObservableProxy.ClearChanged(proxy);
Assert.False(ObservableProxy.IsChanged(proxy));
Assert.Empty(ObservableProxy.ChangedProperties(proxy));
var obj2 = ObservableProxy.Unproxy(proxy);
Assert.Throws<ArgumentException>(() => ObservableProxy.IsChanged(obj2));
Assert.Throws<ArgumentException>(() => ObservableProxy.ChangedProperties(obj2));
Assert.Throws<ArgumentException>(() => ObservableProxy.ClearChanged(obj2));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment