As a part of my (future) MVVM framework, I need to be able to detect changes on an object. To do this, I used the RealProxy
, a C# object that implements Aspect Oriented Programming to "intercept" calls to an object's method.
In my scenario, I'll use the proxy to intercept calls to a _target
's setter to launch an event, PropertyChanged
.
public delegate void PropertyChanged(PropertyChangedArgs args);
/// <summary>
/// Proxy that intercepts call to methods
/// </summary>
/// <typeparam name="T">Type of the target</typeparam>
internal class ObservableObjectProxy<T> : RealProxy where T : MarshalByRefObject
{
private const string SetterMethodStart = "set_";
private readonly T _target;
public event PropertyChanged PropertyChanged;
/// <summary>
/// Creates a new instance of ObservableObjectProxy
/// </summary>
/// <param name="target">Object to proxy</param>
public ObservableObjectProxy(T target) : base(typeof(T))
{
if (target == null) throw new ArgumentNullException(nameof(target));
_target = target;
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
if (methodCall == null)
throw new NotSupportedException("Invocation must be done on a method");
var result = InvokeMethod(methodCall);
if (!IsSetterCall(methodCall))
return result;
var interceptedArgs = new PropertyChangedArgs(methodCall.MethodName.Substring(4),methodCall.GetArg(0));
PropertyChanged?.Invoke(interceptedArgs);
return result;
}
private IMethodReturnMessage InvokeMethod(IMethodCallMessage callMsg)
{
return RemotingServices.ExecuteMessage(_target, callMsg);
}
private static bool IsSetterCall(IMethodCallMessage method)
{
return method.MethodName.StartsWith(SetterMethodStart);
}
}
This object, as you've seen, is internal
. To create it, I'm using a factory.
public interface IObservableObjectFactory
{
T Wrap<T>(T obj) where T : MarshalByRefObject;
T Create<T>() where T : MarshalByRefObject, new();
T Create<T>(params object[] parameters) where T : MarshalByRefObject;
}
/// <summary>
/// Creates or wrap an object that will be observed by the controller.
/// </summary>
public class ObservableObjectFactory : IObservableObjectFactory
{
private readonly IObserverController _observerController;
/// <summary>
/// Creates an instance of ObservableObjectFactory
/// </summary>
/// <param name="observerController">Controller that receives the <code>ObservableObjectProxy</code>'s PropertyChanged event. Cannot be null.</param>
public ObservableObjectFactory(IObserverController observerController)
{
if (observerController == null) throw new ArgumentNullException(nameof(observerController));
_observerController = observerController;
}
/// <summary>
/// Creates a new instance of type <typeparamref name="T"/> that is observed by the controller
/// </summary>
/// <typeparam name="T">Type of the object to create</typeparam>
/// <returns></returns>
public T Create<T>() where T : MarshalByRefObject, new()
{
ObservableObjectProxy<T> proxy = new ObservableObjectProxy<T>(new T());
proxy.PropertyChanged += _observerController.Handle;
return (T)proxy.GetTransparentProxy();
}
/// <summary>
/// Creates a new instance of type <typeparamref name="T"/> that is observed by the controller
/// </summary>
/// <typeparam name="T">Type of the object to create</typeparam>
/// <param name="parameters">Parameters to supply to object's constructor</param>
/// <returns></returns>
public T Create<T>(params object[] parameters) where T : MarshalByRefObject
{
var obj = (T)Activator.CreateInstance(typeof(T), parameters);
ObservableObjectProxy<T> proxy = new ObservableObjectProxy<T>(obj);
proxy.PropertyChanged += _observerController.Handle;
return (T)proxy.GetTransparentProxy();
}
/// <summary>
/// Wraps an existing object in an <code>ObservableObjectProxy</code> that is observed by the controller.
/// </summary>
/// <typeparam name="T">Type of the object to wrap</typeparam>
/// <param name="obj">Object to wrap</param>
/// <returns></returns>
public T Wrap<T>(T obj) where T : MarshalByRefObject
{
ObservableObjectProxy<T> proxy = new ObservableObjectProxy<T>(obj);
proxy.PropertyChanged += _observerController.Handle;
return (T)proxy.GetTransparentProxy();
}
}
This factory is used to create or wrap an object and attach the Handle
method of the controller to the created object. Here's the controller's interface :
public interface IObserverController
{
void Handle(PropertyChangedArgs args);
}
And the args for the event :
public class PropertyChangedArgs
{
public string PropertyName { get; set; }
public object Value { get; set; }
public PropertyChangedArgs(string propertyName, object value)
{
if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
if (value == null) throw new ArgumentNullException(nameof(value));
PropertyName = propertyName;
Value = value;
}
}
I'm looking to know if my code :
- Makes a good use of the
event
pattern. - Makes a good use of the
RealProxy
.
Obviously, anything else is welcomed!