我一直在阅读 Avalonia 的内容,这个示例 显示了添加属性,然后在视图模型构造函数中使用“WhenAnyValue”来引发“RaisePropertyChanged”事件。
重点是什么?
为什么不在属性的 setter 中使用“RaisePropertyChanged”? “WhenAnyValue”方法有什么好处,因为在 setter 中引发事件更加干净。
对于小型项目,所有与业务逻辑稍微脱离的内容都写在
MainWindowViewModel
中,这种方法的用例确实看起来令人困惑。但随着项目规模的扩大,有必要将依赖项注入到 MainWindowViewModel
中,而不是将大部分内容塞进单个类中。
因此,如果您的项目正在利用依赖注入,您将需要这个
WhenAnyValue
方法来观察注入到 MainWindowViewModel
中的其他类中属性的变化,并通知其他注入的东西对变化做出反应。
这是一个例子:
假设您有 2 个复杂的类,
ReactiveCustomer
和 ReactiveBartender
注入到您的 MainWindowViewModel
中。
// MainWindowViewModel class definition
// Contains an instance of ReactiveCustomer and ReactiveBartender as public fields
using System;
using ReactiveUI;
namespace WhyUseWhenAnyValue.ViewModels;
public class MainWindowViewModel : ReactiveObject
{
public MainWindowViewModel()
{
AnReactiveCustomer = new ReactiveCustomer("Rich Ash", 15);
AnReactiveBartender = new ReactiveBartender();
// create an observable by WhenAnyValue that observes the field Age of AnReactiveCustomer
// whenever AnReactiveCustomer's age changes, run the observer in Subscribe
this.WhenAnyValue(x => x.AnReactiveCustomer.Age).Subscribe(
(int newCustomerAge) => {
AnReactiveBartender.GreetingMessage = newCustomerAge ? $"Hello {AnReactiveCustomer.Name}, here is the menu for cocktail." : $"Hello {AnReactiveCustomer.Name}, you can only have mocktails and juice";
}
);
}
public ReactiveCustomer AnReactiveCustomer { get; }
public ReactiveBartender AnReactiveBartender { get; }
}
AnReactiveBartender
如何迎接顾客取决于AnReactiveCustomer
的年龄是否在18岁以上。以下是这两个类别的定义。请注意,它们都继承了ReactiveObject。
// ReactiveBartender class definition. Please note that it also inherits ReactiveObject
// This class is a member of ViewModel namespace
namespace WhyUseWhenAnyValue.ViewModels;
public class ReactiveBartender : ReactiveObject
{
public ReactiveBartender()
{
_greetingMessage = "Default greeting message";
}
public string GreetingMessage
{
get => _greetingMessage;
set => this.RaiseAndSetIfChanged(ref _greetingMessage, value);
}
private string _greetingMessage;
}
// ReactiveCustomer class definition. Please note that it also inherits ReactiveObject
// This class is a member of ViewModel namespace which
// wraps around another class Customer defined in Models namespace
using System.Reactive;
using ReactiveUI;
using WhyUseWhenAnyValue.Models;
namespace WhyUseWhenAnyValue.ViewModels;
public class ReactiveCustomer : ReactiveObject
{
public ReactiveCustomer(string name, int age)
{
_customer = new Customer(name, age);
GrowUpCommand = ReactiveCommand.Create(_reactiveGrowUp);
}
public int Age
{
get => _customer.Age;
set => this.RaiseAndSetIfChanged(ref _customer.Age, value);
}
public string Name
{
get => _customer.Name;
}
public ReactiveCommand<Unit, Unit> GrowUpCommand { get; }
private void _reactiveGrowUp()
{
_customer.GrowUp();
this.RaisePropertyChanged(nameof(_customer.Age));
}
// class Customer is defined in the Models namespace, containing tons of business logic
private Customer _customer;
}
ReactiveCustomer
公开了一个反应式命令GrowUpCommand
,将其年龄增加1。每当客户的年龄增加时,都会引发一个PropertyChanged事件,因为RaiseAndSetIfChanged
字段的设置器中有一个Age
。
调酒师的问候语取决于顾客是否超过饮酒年龄。但是他/她无法接收在
PropertyChanged
类中引发的 ReactiveCustomer
事件,因为 ReactiveBartender
不包含观察 ReactiveCustomer
的机制。 GrowUpCommand 运行 100 次后,顾客年龄为 115 岁,调酒师仍然认为他/她低于饮酒年龄。除非我们从客户和调酒师都居住的MainWindowViewModel
明确通知调酒师更改。
看一下
MainWindowViewModel
的构造函数,有一个由 WhenAnyValue
创建的可观察对象,它观察 Age
的 AnReactiveCustomer
属性。当 AnReactiveCustomer 的年龄更改时,Age
的 setter 会引发 PropertyChanged 事件。可观察对象接收此事件并运行 Subscribe
方法中定义的内容。在 Subscribe
方法中存在所谓的 IObserver
,它是一个 lambda,定义接收到 PropertyChanged
事件后要执行的操作。在这种情况下,观察者检查AnReactiveCustomer
的年龄是否大于18岁,并相应地设置GreetingMessage
的AnReactiveBartender
。
总而言之,以下是在
Age
或 AnReactiveCustomer
更改时发生的一系列操作:
Age
setterMainWindowViewModel
中的IObservable接收事件AnActiveBartender
的问候语信息AnActiveBartender
的 GreetingMessage
setter<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:WhyUseWhenAnyValue.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="WhyUseWhenAnyValue.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="WhyUseWhenAnyValue">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:MainWindowViewModel/>
</Design.DataContext>
<StackPanel>
<TextBlock Text="{Binding AnReactiveBartender.GreetingMessage}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Button Content="GrowUpBro" Command="{Binding AnReactiveCustomer.GrowUpCommand}"/>
</StackPanel>
</Window>
因此,简单地回答您的问题“为什么使用 WhenAnyValue 而不是 RaisePropertyChanged?”,这是因为如果您的
MainWindowViewModel
注入了依赖项,则有必要使用 WhenAnyValue
创建另一个可观察对象来观察一个类的属性实例,并通知其他类实例的更改。 WhenAnyValue
并不意味着要取代RaisePropertyChanged
,它们都有各自的用例。