使用CallerMemberName实现INotifyPropertyChanged的报错排查与解决
问题分析与解决步骤
我来帮你梳理并解决你遇到的这几个问题:
第一个异常:System.TypeLoadException的原因与修复
你最初的PropertyChangedEventArgs类里,给构造函数加了extern修饰符:
public extern PropertyChangedEventArgs(string propertyName);
extern关键字是用来声明由外部代码(比如非托管C++)实现的方法,但你并没有为这个构造函数提供任何外部实现逻辑,所以CLR在加载这个类型时,找不到构造函数的实际执行代码(也就是错误提示里的“no RVA”,相对虚拟地址),直接抛出了TypeLoadException。
修复方法:
去掉extern修饰符,同时给构造函数添加实际的赋值逻辑,就像你后来改写的那样:
public class PropertyChangedEventArgs : EventArgs { public PropertyChangedEventArgs(string propertyName) { PropertyName = propertyName; } public string PropertyName { get; } }
第二个异常:System.NotImplementedException的原因与修复
这个异常完全是因为你注册的事件处理方法Foo_PropertyChanged里直接抛出了未实现异常:
private static void Foo_PropertyChanged(object sender, PropertyChangedEventArgs e) { throw new NotImplementedException(); }
当CustomerName属性变更触发PropertyChanged事件时,这个方法会被执行,自然就会抛出异常。你提到“最终代码可正常运行”,应该是你后续调整了这个方法的实现逻辑。
修复方法:
把这个方法替换成你写的有实际业务逻辑的propertyChanged方法,或者直接修改它的实现,比如:
private static void Foo_PropertyChanged(object sender, PropertyChangedEventArgs e) { var foo = sender as Foo; Console.WriteLine($"属性 {e.PropertyName} 已更新为: {foo?.CustomerName}"); if (e.PropertyName == "CustomerName" && foo?.CustomerName == "dada") { Console.WriteLine("CustomerName成功变更为dada"); } }
完整的可运行代码
把所有问题修复后,完整的可运行代码如下:
using System; using System.Runtime.CompilerServices; namespace Caller_Info_Attributes { public interface INotifyPropertyChanged { event PropertyChangedEventHandler PropertyChanged; } public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e); public class PropertyChangedEventArgs : EventArgs { public PropertyChangedEventArgs(string propertyName) { PropertyName = propertyName; } public string PropertyName { get; } } public class Foo : INotifyPropertyChanged { // 初始化空委托,避免事件为null时调用报错 public event PropertyChangedEventHandler PropertyChanged = delegate { }; void RaisePropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } string customerName; public string CustomerName { get { return customerName; } set { if (value == customerName) return; customerName = value; // 编译器自动传入当前属性名"CustomerName" RaisePropertyChanged(); } } } class Test { static void Main(string[] args) { Foo foo = new Foo(); foo.PropertyChanged += Foo_PropertyChanged; foo.CustomerName = "abra"; foo.CustomerName = "dada"; Console.WriteLine("操作完成"); Console.ReadLine(); } private static void Foo_PropertyChanged(object sender, PropertyChangedEventArgs e) { var fooInstance = sender as Foo; Console.WriteLine($"检测到属性变更: {e.PropertyName} = {fooInstance?.CustomerName}"); if (e.PropertyName == "CustomerName" && fooInstance?.CustomerName == "dada") { Console.WriteLine("CustomerName已更新为目标值dada"); } } } }
额外小提示
[CallerMemberName]特性的核心作用就是让编译器自动把调用该方法的成员名称(这里就是CustomerName属性)作为参数传入,所以你完全不需要手动写RaisePropertyChanged("CustomerName"),编译器会帮你完成这个工作。- 给
PropertyChanged事件初始化一个空委托(= delegate { };)是WPF/VM开发里的常见最佳实践,用来避免事件未被订阅时调用PropertyChanged(...)抛出NullReferenceException。
内容的提问来源于stack exchange,提问作者streamc
相关产品推荐
相关产品推荐

