使用RealProxy实现切面时,TransparentProxy的PropertyChanged绑定失效
问题:RealProxy实现INotifyPropertyChanged切面后UI不更新
我用RealProxy实现了一个切面逻辑:当属性标记为需要通知时,自动触发INotifyPropertyChanged.PropertyChanged事件。但遇到了诡异的问题:
- 调试能看到事件确实触发了,
ComponentModel也在事件调用列表里; - 视图模型内部监听该事件时,能正常收到通知;
- 但绑定的UI完全没有更新,完全没反应。
以下是问题复现代码(补全了关键部分):
using System; using System.ComponentModel; using System.Runtime.Remoting.Proxies; using System.Runtime.Remoting.Messaging; using System.Runtime.CompilerServices; // RealProxy实现类 public class NotifyProxy<T> : RealProxy where T : class, INotifyPropertyChanged { private readonly T _target; public NotifyProxy(T target) : base(typeof(T)) { _target = target; // 内部监听验证事件是否触发 _target.PropertyChanged += (s, e) => Console.WriteLine($"内部监听:{e.PropertyName} 变更"); } public override IMessage Invoke(IMessage msg) { if (msg is IMethodCallMessage methodCall) { // 拦截属性set方法 if (methodCall.MethodName.StartsWith("set_")) { string propertyName = methodCall.MethodName.Substring(4); // 假设这里判断属性需要通知(比如通过自定义特性) if (true) { // 执行原属性赋值 var result = methodCall.MethodBase.Invoke(_target, methodCall.InArgs); // 触发PropertyChanged事件 _target.PropertyChanged?.Invoke(_target, new PropertyChangedEventArgs(propertyName)); return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall); } } } return base.Invoke(msg); } } // 视图模型 public class MyViewModel : INotifyPropertyChanged { private string _name; public string Name { get => _name; set => _name = value; } public event PropertyChangedEventHandler PropertyChanged; } // 使用示例 class Program { static void Main() { var vm = new MyViewModel(); var proxy = new NotifyProxy<MyViewModel>(vm); var proxiedVm = (MyViewModel)proxy.GetTransparentProxy(); // 设置属性,触发切面逻辑 proxiedVm.Name = "Test"; // 控制台能打印内部监听日志,但UI绑定没反应 } }
核心原因分析
1. 事件发送者不匹配
UI绑定的是透明代理对象,但你在切面里触发事件时,发送者是原始的_target视图模型对象。WPF/WinForms的绑定系统会校验事件发送者是否和绑定源一致,不一致的话就会忽略这个通知——这就是为什么内部监听能收到,但UI没反应的关键!
2. RealProxy的动态类型兼容性问题
透明代理的实际类型是CLR动态生成的,不是你定义的MyViewModel。虽然它实现了INotifyPropertyChanged,但UI框架的绑定系统可能无法正确识别这个动态类型的通知逻辑,导致通知被丢弃。
解决方案
方案1:修改事件发送者为透明代理
这是最直接的修复方式,把事件触发的sender改成透明代理对象,让绑定系统认为通知来自绑定源本身:
// 在Invoke方法中修改事件触发代码 public override IMessage Invoke(IMessage msg) { if (msg is IMethodCallMessage methodCall) { if (methodCall.MethodName.StartsWith("set_")) { string propertyName = methodCall.MethodName.Substring(4); if (true) { var result = methodCall.MethodBase.Invoke(_target, methodCall.InArgs); // 获取透明代理对象作为sender var proxyInstance = GetTransparentProxy(); PropertyChangedEventHandler handler = _target.PropertyChanged; handler?.Invoke(proxyInstance, new PropertyChangedEventArgs(propertyName)); return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall); } } } return base.Invoke(msg); }
方案2:放弃RealProxy,改用成熟的AOP框架
RealProxy是.NET Remoting时代的技术,在现代UI场景下兼容性很差。推荐用基于IL织入的框架,比如:
- Fody.PropertyChanged:零侵入自动实现INotifyPropertyChanged,只需给类加个特性,完全不用写切面。
- PostSharp:强大的AOP框架,支持自定义属性变更通知逻辑。
以Fody为例,只需要两步:
- 安装NuGet包
PropertyChanged.Fody - 给视图模型加特性:
[AddINotifyPropertyChangedInterface] public class MyViewModel { public string Name { get; set; } }
这样框架会自动在编译时给属性添加通知逻辑,完全避免RealProxy的坑。
方案3:手动实现INotifyPropertyChanged(最稳妥)
如果切面不是必须的,直接在视图模型里手动写通知逻辑是最可靠的:
public class MyViewModel : INotifyPropertyChanged { private string _name; public string Name { get => _name; set { if (_name != value) { _name = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
验证步骤
- 先试方案1,修改sender后看UI是否更新;
- 如果还是不行,检查线程问题:WPF中必须在Dispatcher线程触发事件,可以用
Application.Current.Dispatcher.Invoke包裹事件触发代码; - 长期来看,建议替换Fody这类成熟框架,避免RealProxy的兼容性问题。
内容的提问来源于stack exchange,提问作者bixarrio
相关产品推荐
相关产品推荐

