我对通知属性有种执念
- 要兼容低版本
- 要写起来短小精悍
- 要最好无依赖
- 哪有那么好的事情!
必备代码
所有衍生的方式都将基于此操作,你可以封装到类中继承
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
原始方式
这种方式,
- 你必须要接受一个不需要的后台变量
- 硬编码的名字写法
你也可以使用nameof进行操作,
但需要注意的是nameof是编译时的动作,不要让混淆器来捣乱
private int myVar;
public int MyProperty
{
get { return myVar; }
set
{
myVar = value;
//通知绑定系统这个名字的属性有变化
RaisePropertyChanged("MyProperty");
}
}
使用[CallerMemberName]
隐藏属性名字的主动编写
在RaisePropertyChanged
的属性上加上[CallerMemberName]
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
写法就变成了
private int myVar;
public int MyProperty
{
get { return myVar; }
set
{
myVar = value;
//在编译时将会为第一个参数自动传入 "MyProperty"
RaisePropertyChanged();
//如果没有.NET版本没有[CallerMemberName],也可以使用表达式树来操作,这个操作没有硬编码,是运行时操作
// RaisePropertyChanged(()=>MyProperty);
}
}
进一步干掉后台变量
通过基类将[CallerMemberName]的名字保存到后台统一字典来进行变量值的管理
这也是我目前使用的方法
public int MyProperty
{
get { return GetProperty(); }
set { SetProperty(value); }
}
优化成一行
public int MyProperty { get => GetProperty(); set => SetProperty(value); }
使用Emit动态生成
你的属性这样写
那个virtual表明了它的派生类可以覆盖它
public class MyViewModel : ViewModelBase
{
public virtual int MyProperty { get; set; }
}
现在使用Emit动态创建一个派生类
在实例化后,返回给你基类,看似什么都没有发生,但你的属性却得到了通知能力
动态创建的代码类似下方所示:
public class NullViewModel_EXTEND : MyViewModel
{
private int myVar;
public override int MyProperty
{
get { return myVar; }
set
{
myVar = value;
RaisePropertyChanged("MyProperty");
}
}
}
由于是动态创建的,可能调试会有些麻烦
此操作和js的代理一样,可以为原有对象注入拦截代码,只不过c#这个操作有亿点点麻烦
实现参考: https://www.codewrecks.com/post/old/2008/08/implement-inotifypropertychanged-with-dynamic-code-generation/
使用后台线程轮训检查属性值是否变化
这是我觉得最离谱的实现,但它能跑,怕什么!
地址: https://github.com/michael-damatov/shuriken
看起来还挺流畅
还有一种没太具体了解的方式
在maui早期mvu模式演示上见过
猜测这个State的参数里面也包含了CallerMemberName
然后重写ToString,重写隐式转换来达到使用起来像正常属性的操作
public class MyPage : View {
readonly State<int> clickCount = new State<int> (1);
public MyPage() {
Body = () => new VStack {
new Text (() => $"Click Count: {clickCount}"),
new Button("Update Text", () => {
clickCount.Value++;
}
};
}
}
下面2种属于无需动手的操作
使用Fody/PropertyChanged插件
此插件会在你的代码编译后对属性进行自动通知注入
你只需要像正常编写属性一样
使用Source Generator自动生成
与Fody类似,可以在编译时为你自动生成通知代码