在值更改之前调用的城堡动态代理 PropertyChanged
castle dynamic proxy PropertyChanged called before value changed
我用下面的代码创建了一个单元测试库,它尝试代理一个 INotifyPropertyChanged,并监听它。
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Castle.Core;
using Castle.DynamicProxy;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Component = Castle.MicroKernel.Registration.Component;
namespace UnitTestWinsorContainer {
[TestClass]
public class UnitTestDynamicProxy {
public static IWindsorContainer Container { get; protected set; }
[ClassInitialize]
public static void TestInit(TestContext tc) {
Container = new WindsorContainer();
Container.Register(Classes.FromThisAssembly().BasedOn<IInterceptor>());
Container.Register(Component.For(typeof(DummyViewModel))
.Interceptors(InterceptorReference.ForType<DummyInterceptor>()).Anywhere);
}
[TestMethod]
public void TestResolve() {
Console.WriteLine("Test Starts");
var i = Container.Resolve<DummyViewModel>();
Assert.AreEqual(i.GetType().Name, "DummyViewModelProxy");
i.PropertyChanged += OnPropertyChanged;
i.Qty = 100;
Console.WriteLine($"Get Qty after PropertyChanged of Proxy Qty=[{i.Qty}]");
var j = new DummyViewModel();
j.PropertyChanged += OnPropertyChanged;
j.Qty = 100;
}
private static void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
var d = sender as DummyViewModel;
if (d != null) Console.WriteLine($"PropertyName=[{e.PropertyName}] Qty=[{d.Qty}]");
}
}
public class DummyInterceptor : IInterceptor {
public void Intercept(IInvocation invocation) {
invocation.Proceed();
}
}
public class DummyViewModel : Bindable {
private int qty;
/// <summary>
/// My Property
/// </summary>
public int Qty {
get { return qty; }
set { SetProperty(ref qty, value); }
}
}
public class Bindable : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) {
if (Equals(storage, value)) return false;
storage = value;
RaisePropertyChanged(propertyName);
return true;
}
protected virtual bool SetProperty<T>(ref T storage, T value, Action onChanged,
[CallerMemberName] string propertyName = null) {
if (Equals(storage, value)) return false;
storage = value;
onChanged?.Invoke();
RaisePropertyChanged(propertyName);
return true;
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) {
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) {
PropertyChanged?.Invoke(this, args);
}
}
}
测试会成功,但输出控制台显示
Test Starts
PropertyName=[Qty] Qty=[0]
Get Qty after PropertyChanged of Proxy Qty=[100]
PropertyName=[Qty] Qty=[100]
这意味着当基础值未更改时调用代理的第一个 属性 更改事件。
这是为什么?我做错了什么吗?
问题在于 Castle 如何拦截带有 ref
参数的方法。我相信下面生成的代理 class 看起来像:
public class DummyViewModelProxy : DummyViewModel
{
protected override bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
var interceptor = new DummyInterceptor();
IInvocation invocation = new Invocation(storage, value, propertyName);
interceptor.Intercept(invocation);
storage = (T)invocation.Arguments[0];
return (bool)invocation.ReturnValue;
}
....
}
然后ref
参数链被打断,值在PropertyChanged
被调用后才真正设置。
我用下面的代码创建了一个单元测试库,它尝试代理一个 INotifyPropertyChanged,并监听它。
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Castle.Core;
using Castle.DynamicProxy;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Component = Castle.MicroKernel.Registration.Component;
namespace UnitTestWinsorContainer {
[TestClass]
public class UnitTestDynamicProxy {
public static IWindsorContainer Container { get; protected set; }
[ClassInitialize]
public static void TestInit(TestContext tc) {
Container = new WindsorContainer();
Container.Register(Classes.FromThisAssembly().BasedOn<IInterceptor>());
Container.Register(Component.For(typeof(DummyViewModel))
.Interceptors(InterceptorReference.ForType<DummyInterceptor>()).Anywhere);
}
[TestMethod]
public void TestResolve() {
Console.WriteLine("Test Starts");
var i = Container.Resolve<DummyViewModel>();
Assert.AreEqual(i.GetType().Name, "DummyViewModelProxy");
i.PropertyChanged += OnPropertyChanged;
i.Qty = 100;
Console.WriteLine($"Get Qty after PropertyChanged of Proxy Qty=[{i.Qty}]");
var j = new DummyViewModel();
j.PropertyChanged += OnPropertyChanged;
j.Qty = 100;
}
private static void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
var d = sender as DummyViewModel;
if (d != null) Console.WriteLine($"PropertyName=[{e.PropertyName}] Qty=[{d.Qty}]");
}
}
public class DummyInterceptor : IInterceptor {
public void Intercept(IInvocation invocation) {
invocation.Proceed();
}
}
public class DummyViewModel : Bindable {
private int qty;
/// <summary>
/// My Property
/// </summary>
public int Qty {
get { return qty; }
set { SetProperty(ref qty, value); }
}
}
public class Bindable : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) {
if (Equals(storage, value)) return false;
storage = value;
RaisePropertyChanged(propertyName);
return true;
}
protected virtual bool SetProperty<T>(ref T storage, T value, Action onChanged,
[CallerMemberName] string propertyName = null) {
if (Equals(storage, value)) return false;
storage = value;
onChanged?.Invoke();
RaisePropertyChanged(propertyName);
return true;
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) {
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) {
PropertyChanged?.Invoke(this, args);
}
}
}
测试会成功,但输出控制台显示
Test Starts
PropertyName=[Qty] Qty=[0]
Get Qty after PropertyChanged of Proxy Qty=[100]
PropertyName=[Qty] Qty=[100]
这意味着当基础值未更改时调用代理的第一个 属性 更改事件。
这是为什么?我做错了什么吗?
问题在于 Castle 如何拦截带有 ref
参数的方法。我相信下面生成的代理 class 看起来像:
public class DummyViewModelProxy : DummyViewModel
{
protected override bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
var interceptor = new DummyInterceptor();
IInvocation invocation = new Invocation(storage, value, propertyName);
interceptor.Intercept(invocation);
storage = (T)invocation.Arguments[0];
return (bool)invocation.ReturnValue;
}
....
}
然后ref
参数链被打断,值在PropertyChanged
被调用后才真正设置。