WPF 实现通知属性的N种方式

[删除(380066935@qq.com或微信通知)]

更好的阅读体验请查看原文:https://www.cnblogs.com/trykle/p/17016385.html

我对通知属性有种执念

  • 要兼容低版本
  • 要写起来短小精悍
  • 要最好无依赖
  • 哪有那么好的事情!

必备代码

所有衍生的方式都将基于此操作,你可以封装到类中继承

public event PropertyChangedEventHandler PropertyChanged;

protected void RaisePropertyChanged(string propertyName)
{
	PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

原始方式

这种方式,

  1. 你必须要接受一个不需要的后台变量
  2. 硬编码的名字写法
    你也可以使用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类似,可以在编译时为你自动生成通知代码