WPF MVVM 框架学习总结(MvvmLight / CommunityToolkit.Mvvm / Prism)
一、三大框架总览与对比
WPF 中 MVVM 框架的发展脉络:
INotifyPropertyChanged / ICommand
└──> MvvmLight(GalaSoft,个人维护,已基本停更)
└──> Microsoft Toolkit.Mvvm(曾用名)
└──> CommunityToolkit.Mvvm(现名,微软官方维护)
└──> Prism(企业级,Patterns & Practices / .NET 基金会)
| 维度 | MvvmLight | CommunityToolkit.Mvvm | Prism |
|---|---|---|---|
| 定位 | 轻量级、入门简单 | 微软官方轻量级 MVVM 工具集 | 企业级框架,含模块化/导航 |
| 维护方 | Laurent Bugnion(个人) | Microsoft(官方) | .NET 基金会 |
| 适用场景 | 小型/遗留项目 | 全场景(轻量→中型),新项目首选 | 大型/复杂/模块化项目 |
| 核心组件 | ViewModelBase、RelayCommand、Messenger |
ObservableObject、[ObservableProperty]、[RelayCommand]、WeakReferenceMessenger |
BindableBase、DelegateCommand、IEventAggregator、区域/导航/模块 |
| 依赖 | 无强依赖 | 无依赖(.NET Standard) | 依赖 Prism.Core / Prism.Wpf 等 |
| 运行时性能 | 一般(运行时反射) | 最优(编译时源生成器) | 中等 |
| DI 容器 | 内置 SimpleIoc |
适配第三方(MS DI / Autofac) | 内置 DryIoc / Unity |
核心联系:三者都遵循 MVVM 模式,核心目标都是解耦 View 与 ViewModel,都提供「可绑定的基类 + 命令封装 + 组件间通信」三件套。
二、MVVM 公共核心概念
无论用哪个框架,MVVM 的本质都是:
- View:XAML 界面,负责显示,不写业务逻辑。
- ViewModel :为 View 提供可绑定的属性(带通知)和命令(执行动作)。
- Model:业务数据模型(含数据库访问、服务)。
三个基本功:
- 属性变更通知 :派发
INotifyPropertyChanged,UI 才能感知数据变化。 - 命令 :封装
ICommand,让可执行逻辑与 UI 解耦。 - 组件通信:跨页面/ViewModel 传递数据(消息/事件)。
三、MvvmLight 知识点
3.1 核心组件
| 组件 | 作用 |
|---|---|
ViewModelBase |
VM 基类,实现 INotifyPropertyChanged,提供 Set(ref field, value) |
RelayCommand / RelayCommand<T> |
命令类,替代手动实现 ICommand |
Messenger |
消息传递器,跨 VM/视图解耦通信 |
ViewModelLocator |
统一管理 VM 实例(配合 SimpleIoc) |
3.2 属性通知:ViewModelBase 的 Set
csharp
public class MainViewModel : ViewModelBase
{
private string _welcomeMessage;
public string WelcomeMessage
{
get => _welcomeMessage;
set => Set(ref _welcomeMessage, value); // Set 自动触发 PropertyChanged
}
}
3.3 命令:RelayCommand
csharp
public ICommand ClickCommand { get; }
public MainViewModel()
{
// 参数1:执行逻辑;参数2(可选):是否可执行
ClickCommand = new RelayCommand(OnClick, CanClick);
}
private void OnClick() => _clickCount++;
private bool CanClick() => _clickCount < 5;
3.4 消息传递:Messenger.Default
csharp
// 定义消息类型
public class UpdateMessage { public string Content { get; set; } }
// 发送
Messenger.Default.Send(new UpdateMessage { Content = "子窗口更新了数据" });
// 接收(构造函数中注册,销毁时注销,避免内存泄漏)
Messenger.Default.Register<UpdateMessage>(this, msg => WelcomeMessage = msg.Content);
public override void Cleanup()
{
Messenger.Default.Unregister<UpdateMessage>(this);
base.Cleanup();
}
3.5 ViewModelLocator + SimpleIoc(DI)
MvvmLight 用 ViewModelLocator 把 VM 注册进 SimpleIoc,视图通过 DataContext="{Binding Source={StaticResource Locator}, Path=Main}" 获取。
csharp
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>(); // VM(单例)
SimpleIoc.Default.Register<LoginViewModel>();
SimpleIoc.Default.Register<IService<User, ViewUser>, UserService>(); // 服务
}
public MainViewModel Main => ServiceLocator.Current.GetInstance<MainViewModel>();
public LoginViewModel Login => ServiceLocator.Current.GetInstance<LoginViewModel>();
}
xml
<!-- App.xaml 注册定位器 -->
<Application.Resources>
<local:ViewModelLocator x:Key="Locator" />
</Application.Resources>
<!-- 视图绑定 -->
<Window DataContext="{Binding Main, Source={StaticResource Locator}}">
3.6 设计时数据
csharp
if (IsInDesignMode)
WelcomeMessage = "设计时预览文本"; // XAML 设计器预览
else
WelcomeMessage = "运行时文本";
3.7 常见坑
- 属性一定要用
Set,直接赋值不会触发通知。 RelayCommand可执行状态变化时手动RaiseCanExecuteChanged()。Messenger必须Unregister,或用弱引用注册。SimpleIoc需 VM 有无参构造函数。
四、CommunityToolkit.Mvvm 知识点
4.1 最大亮点:源生成器(Source Generator)
通过 C# 特性 ,编译期自动生成属性/命令底层代码,无反射开销、零样板代码。核心要求:
- 类必须是
partial,且继承ObservableObject; - 命令方法需为
private(或非 public),方法名不需要写Command后缀。
csharp
public partial class MainViewModel : ObservableObject
{
// 自动生成 Name 属性 + PropertyChanged 通知
[ObservableProperty]
private string? name = "张三";
// 自动生成 FirstCommand(无参)
[RelayCommand]
private void First() => MessageBox.Show(Name + "sss");
// 自动生成 SecondCommand,支持参数
[RelayCommand]
private void Second(object o) => MessageBox.Show(o.ToString() + "sss");
}
4.2 属性写法对比
csharp
// 写法1:手写 + OnPropertyChanged
private string name;
public string Name
{
get => name;
set { name = value; OnPropertyChanged(); }
}
// 写法2:SetProperty(推荐,等价 Hand)
public string Name { get => name; set => SetProperty(ref name, value); }
// 写法3(错误):name = value; SetProperty(ref name, value); 旧值已覆盖,不会通知
// 写法4(最佳):源生成器
[ObservableProperty] private string? name;
4.3 命令:RelayCommand / AsyncRelayCommand
csharp
public RelayCommand FirstCommand => new RelayCommand(() =>
MessageBox.Show("无参数命令"));
public RelayCommand<object> SecondCommand => new RelayCommand<object>(o =>
MessageBox.Show("带参数命令 " + o.ToString()));
public AsyncRelayCommand<object> ThridCommand => new AsyncRelayCommand<object>(async o =>
{
await Task.Delay(1000);
MessageBox.Show("异步命令 " + o.ToString());
});
源生成器方式(自动生成命令、自动监听属性更新可执行状态):
csharp
[RelayCommand(CanExecute = nameof(CanClick))]
private void Click() { ClickCount++; }
private bool CanClick() => ClickCount < 5;
// 异步 + 取消
[RelayCommand]
private async Task LoadAsync(CancellationToken token)
{
token.ThrowIfCancellationRequested();
await Task.Delay(100, token);
}
4.4 弱引用消息传递:WeakReferenceMessenger
建议提前定义消息类型 (绝不直接 Send(实体),否则会造成消息串扰、语义不清):
csharp
// 消息类:继承内置 ValueChangedMessage<T>,自动拥有 Value 属性
public class LoginInfoMessage : ValueChangedMessage<User>
{
public LoginInfoMessage(User value) : base(value) { }
}
// 发送:弱引用,接收方不再需要手动 Unregister,GC 自动回收
WeakReferenceMessenger.Default.Send(new LoginInfoMessage(u1));
csharp
// 接收:推荐继承 ObservableRecipient(内置 Messenger,可自动取消订阅)
public partial class MainViewModel : ObservableRecipient
{
public MainViewModel()
{
IsActive = true; // 开启消息接收
Messenger.Register<LoginInfoMessage>(this, (r, m) => {
var user = m.Value; // 取到传递的 User
});
}
}
弱引用优势:接收方关闭窗口且无强引用时 GC 可回收,避免内存泄漏(强引用忘
Unregister会导致 VM 永远无法回收)。
4.5 属性变更回调
源生成器会生成 partial void On[Name]Changed(...),可重写自定义逻辑:
csharp
// private string? _welcomeMessage; 对应生成 WelcomeMessage
partial void OnWelcomeMessageChanged(string oldValue, string newValue)
{
Console.WriteLine($"从 {oldValue} 变为 {newValue}");
}
4.6 依赖注入(适配 MS DI)
CommunityToolkit.Mvvm 不内置 DI ,但可无缝配 Microsoft.Extensions.DependencyInjection:
csharp
public partial class App : Application
{
public App() => Services = ConfigureServices();
public new static App Current => (App)Application.Current;
public IServiceProvider Services { get; }
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddTransient<Main>(); // 窗口(瞬时)
services.AddSingleton<MainViewModel>(); // VM(单例)
services.AddSingleton<IPeople<User, ViewUser>, UserService>(); // 服务
return services.BuildServiceProvider();
}
}
VM 中通过构造函数注入服务,运行时用 App.Current.Services.GetService<MainViewModel>() 或注入 IServiceProvider 获取窗口/对象:
csharp
public MainViewModel(IPeople<User, ViewUser> services, IServiceProvider sp)
{
_services = services;
i2 = sp;
}
private void Open()
{
i2.GetService<AnimalWindow>().ShowDialog(); // 由容器创建窗体,避免 VM 耦合窗体
}
4.6.1 GetService 与 GetRequiredService 对比
容器解析对象主要有两种方式:GetService<T>() 与 GetRequiredService<T>()(位于 Microsoft.Extensions.DependencyInjection 命名空间)。
| 对比项 | GetService<T>() |
GetRequiredService<T>() |
|---|---|---|
| 服务未注册 | 返回 null,不会抛异常 |
抛 InvalidOperationException,第一时间暴露配置错误 |
| 适用场景 | 服务「可有可无」,可空判断后再使用 | 服务「必须存在」,缺失时尽快失败 |
| 返回值声明 | 可返回空引用(易造成后续 NRE) | 保证非空(框架负责检查) |
示例(都取自 IServiceProvider 实例,如 App.Current.Services):
csharp
// 写法1:GetService ------ 可能返回 null,需自行判空
AnimalWindow? win = App.Current.Services.GetService<AnimalWindow>();
if (win != null)
{
win.ShowDialog();
}
// 写法2:GetRequiredService ------ 未注册直接抛异常,强约束
AnimalWindow win2 = App.Current.Services.GetRequiredService<AnimalWindow>();
win2.ShowDialog();
使用建议:
- 日常获取 VM、窗体、服务等必需对象 时,优先用
GetRequiredService<T>(),运行期出错能立刻提示「哪个服务没注册」,避免看到难以排查的空引用异常。 - 仅当某个服务可选(如日志、可选的功能模块)时才用
GetService<T>(),并配合?.判空。 - 两者本质都是「从容器取出已注册实例」,区别只在未注册时的失败策略 ;若未先
services.AddSingleton/AddTransient(...)注册,两者都无法取到对象。
4.7 常见坑
- 源生成器不生效:确保 VS2022、C# ≥ 9.0(
<LangVersion>9.0</LangVersion>)、清理重建。 - 命令绑定失败:命令方法须为
private;方法名Click生成命令ClickCommand。 - 消息不触发:
ObservableRecipient需IsActive = true;订阅/发送须为同一消息类型。
五、Prism 知识点
5.1 核心功能清单
| 功能 | 说明 |
|---|---|
| Region(区域管理) | 用 <ContentControl> 等作为容器动态放视图,替代 <Frame> |
| ViewModelLocationProvider | 按约定自动关联 View 与 ViewModel(自动绑定 DataContext) |
| Command(命令) | DelegateCommand / DelegateCommand<T>(等同 RelayCommand) |
| Navigation(导航) | 配合区域实现页面跳转,支持传参/导航日志/导航守卫 |
| Dialog(对话框) | 可将 UserControl 以对话框形式展示 |
| Module(模块化) | 大型项目拆分独立模块(IModule) |
| IEventAggregator | 跨 VM/模块通信 |
| DI(依赖注入) | 内置 DryIoc / Unity 容器 |
5.2 把普通 WPF 项目改造为 Prism
App.xaml :<Application> 改为 <prism:PrismApplication>,去掉 StartupUri,加命名空间。
xml
<prism:PrismApplication x:Class="PrismDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/">
<Application.Resources></Application.Resources>
</prism:PrismApplication>
App.xaml.cs :App 继承 PrismApplication,重写 CreateShell()、RegisterTypes():
csharp
public partial class App : PrismApplication
{
// 创建主窗口(从容器解析,自动注入 DataContext)
protected override Window CreateShell() => Container.Resolve<MainWindow>();
// 登录校验:启动时弹出登录窗,取消则关闭程序
protected override void InitializeShell(Window shell)
{
Login l1 = Container.Resolve<Login>();
if (l1 == null || l1.ShowDialog() != true)
Application.Current.Shutdown();
}
// 注册服务与可导航视图
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<Login>();
containerRegistry.RegisterForNavigation<Page1>();
containerRegistry.RegisterForNavigation<Page2>();
}
// 模块化
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
base.ConfigureModuleCatalog(moduleCatalog);
moduleCatalog.AddModule<Module1Module>();
}
}
5.3 ViewModel:BindableBase
csharp
public class MainViewModel : BindableBase
{
private string _title = "Prism Application";
public string Title
{
get => _title;
set => SetProperty(ref _title, value); // 自动触发通知
}
}
5.4 自动绑定 ViewModel(约定优于配置)
满足三条约定 即可免写 this.DataContext:
- 视图(Window/UserControl/Page)放在
Views文件夹; - 视图模型放在
ViewModels文件夹; - 名称格式:
视图名 + ViewModel(如MainWindow→MainWindowViewModel)。
若用 Show() / ShowDialog() 手动打开窗体,需手动加:
xml
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
5.5 区域(Region)管理:定义与打开方式
概念 :区域(Region)是 Prism 中用来「动态容纳视图」的占位容器,替代传统 <Frame> / <ContentControl>。你负责把视图放进去,展示的位置由区域决定。
定义方式 :在 XAML 中用附加属性 prism:RegionManager.RegionName="区域名" 标记某控件为区域(常用于 <ContentControl>、<ItemsControl>、<TabControl>):
xml
<ContentControl prism:RegionManager.RegionName="ContentRegion"/>
打开方式(两种):
- 视图注入(固定投放)------启动时直接把视图放进来,之后不切换:
csharp
public MainWindowViewModel(IRegionManager m)
{
_m = m;
_m.RegisterViewWithRegion<Header>("HeaderRegion"); // Header 显示在 HeaderRegion
_m.RegisterViewWithRegion<Left>("LeftRegion");
}
- 拿到区域对象再操作 ------通过
_m.Regions["区域名"]取出区域(IRegion),再调用其方法:
csharp
IRegion region = _m.Regions["ContentRegion"];
region.RequestNavigate("Home");
区域适配器:让控件能被当作区域来放置视图,基类
RegionAdapterBase<T>。内置ContentControlRegionAdapter(ContentControl)、ItemsControlRegionAdapter(ItemsControl)、SelectorRegionAdapter(ComboBox/ListBox/TabControl/Ribbon)。
5.6 导航(Navigation):定义与打开方式
概念 :导航是一组「在区域内动态切换视图」的机制。
定义方式 :可导航视图必须先通过 RegisterForNavigation<T>() 注册进容器(App.xaml.cs):
csharp
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<User>();
containerRegistry.RegisterForNavigation<Order>();
containerRegistry.RegisterForNavigation<Home>();
}
打开方式 :在 VM 中借助 IRegionManager(Prism 自动注入)调用 RequestNavigate("区域名", "视图名"):
csharp
public DelegateCommand SelectChangedCommand =>
new DelegateCommand(() =>
{
_m.RequestNavigate("ContentRegion", SelectedItem.PageName); // 区域名 + 视图名
});
导航打开方式的进阶形式 (2区域和导航 项目):带参数 + 回调日志。
csharp
NavigationParameters navPars = new NavigationParameters();
navPars.Add("name", "张三");
navPars.Add("age", 10);
_m.RequestNavigate("ContentRegion", page, par =>
{
if (par.Result == true) // 跳转成功
Journal = par.Context.NavigationService.Journal; // 导航日志
// par.Context.NavigationService / par.Context.Uri / par.Context.Parameters
}, navPars);
5.7 区域 vs 导航:定义方式与打开方式对比
一句话区分:区域是「容器」负责视图放哪;导航是「动作」负责如何把视图放入/换出区域。
| 对比项 | 区域(Region) | 导航(Navigation) |
|---|---|---|
| 本质 | 一个占位「容器对象」 | 一组「切换视图的行为/机制」 |
| 定义方式 | XAML 标记 prism:RegionManager.RegionName="区域名" |
视图须 RegisterForNavigation<T>() 注册进容器 |
| 打开方式 A | RegisterViewWithRegion<View>("区域名")(固定投放,不可切换) |
RequestNavigate("区域名", "视图名")(动态切换) |
| 打开方式 B | _m.Regions["区域名"] 取出区域对象再操作 |
进阶:RequestNavigate + NavigationParameters 传参 / 回调获取日志 |
| 能否切换视图 | 不能(一次投放固定) | 能(随时切换、可前进/后退) |
| 是否需要注册 | 否 | 是(必须先 RegisterForNavigation) |
| 典型场景 | 固定布局:头部、侧边栏、抽屉 | 页面流转:菜单切换、翻页、Tab |
协作关系 :导航一定要落在某个区域内(RequestNavigate 第一个参数就是区域名);区域提供一个「被导航的场地」。实际项目通常先定义区域,再注册导航视图,最后在菜单选中事件里 RequestNavigate。
5.8 导航深入:传参、导航确认、导航日志
- 传参 :
NavigationParameters对象,通过RequestNavigate的第 三/四 个参数传递。 - 接收 :目标 VM 实现
INavigationAware,重写:OnNavigatedTo(NavigationContext context):到达某视图;OnNavigatedFrom(NavigationContext context):离开某视图;IsNavigationTarget(NavigationContext context):返回布尔值,true重用视图(性能高)、false重建。
- 导航确认/守卫 :实现
IConfirmNavigationRequest.ConfirmNavigationRequest(context, callback),时机应写在离开的页面。 - 导航日志 :
IRegionNavigationJournal,提供GoForward()/GoBack()/Clear(),判断用CanGoForward/CanGoBack。
5.9 命令:DelegateCommand
csharp
public DelegateCommand ClickCommand { get; }
public DelegateCommand<string> ParamCommand { get; }
public PageAViewModel()
{
ClickCommand = new DelegateCommand(OnClick);
ParamCommand = new DelegateCommand<string>(OnParamClick);
// 监听属性变化自动刷新 CanExecute:
// new DelegateCommand(OnClick, CanClick).ObservesProperty(() => _count);
}
5.10 事件聚合器:IEventAggregator
csharp
// 定义事件
public class MessageEvent : PubSubEvent<string> { }
// 发送方:构造注入 IEventAggregator
_eventAggregator.GetEvent<MessageEvent>().Publish("来自 Sender 的消息");
// 接收方:订阅(可指定 UI 线程)
eventAggregator.GetEvent<MessageEvent>()
.Subscribe(OnMessageReceived, ThreadOption.UIThread);
5.11 对话框服务:IDialogService / IDialogAware
Prism 的对话框服务可把用户控件(UserControl当对话框弹出,替代原生 Window。
定义方式 :把用户控件注册为对话框,并让其对应的 VM 实现 IDialogAware。
csharp
// App.xaml.cs:注册对话框
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterDialog<Login>(); // 把 Login 用户控件注册成一个对话框
}
打开方式 :注入 IDialogService,调用 ShowDialog("对话框名", 回调):
csharp
service.ShowDialog("Login", d =>
{
if (d.Result != ButtonResult.OK)
{
System.Environment.Exit(0); // 登录取消则退出
}
});
对话框的 VM 必须实现 IDialogAware 四个成员:
csharp
public class LoginViewModel : BindableBase, IDialogAware
{
public string Title => "登录窗体"; // 标题
public event Action<IDialogResult> RequestClose; // 请求关闭事件
public bool CanCloseDialog() => true; // 是否可关闭
public void OnDialogClosed() { } // 关闭时触发
public void OnDialogOpened(IDialogParameters parameters) { } // 打开时触发
public DelegateCommand LoginCommand => new DelegateCommand(() =>
{
if (User.Name == "admin" && User.Psw == "123456")
RequestClose?.Invoke(new DialogResult(ButtonResult.OK)); // 关闭并返回结果
});
}
相比原生 Window,对话框服务的好处是:无窗体(解耦合)、可在关闭时统一回传 IDialogResult、支持传参 IDialogParameters。
5.12 模块化开发(IModule)
概念 :模块化将复杂系统拆分成多个相对独立、功能单一的类库项目 ,每个模块有自己的 Views / ViewModels / 配置。
优势与应用场景 :简化开发、提高复用、降低耦合、增强扩展性;适合中大型项目(小项目用属过度设计)。
模块开发步骤:
- 新建 WPF 类库(模块项目),结构与主项目一致(Views、ViewModels 等);
- 实现
IModule接口(两个核心方法RegisterTypes、OnInitialized)。
csharp
public class Module1Module : IModule
{
// 模块初始化(加载后逻辑,如默认导航)
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RequestNavigate("ContentRegion", "Home");
}
// 注册模块内的视图/服务
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA, ViewAViewModel>();
containerRegistry.RegisterForNavigation<ViewB>();
}
}
主应用注册模块 (App.xaml.cs 的 ConfigureModuleCatalog):
csharp
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
base.ConfigureModuleCatalog(moduleCatalog);
moduleCatalog.AddModule<Module1Module>();
}
模块加载方式 :代码方式(推荐,AddModule<T>)、配置文件方式(ConfigurationModuleCatalog)、目录路径方式(DirectoryModuleCatalog,按路径扫描)、手动加载(了解)。核心接口:IModule、IModuleCatalog。
与类库的区别:Prism 模块除了拆分代码,还包含「视图/视图模型/模块配置」,并可通过模块目录动态加载,比普通类库更适合做插件化、可插拔的扩展。
5.13 Prism Template Pack(模板 + 代码片段)
VS 扩展「Prism Template Pack」提供模板:Prism Full App、Prism Blank App、Prism Module,并提供代码片段:
prismprop:生成带通知的属性;prismcmd:生成DelegateCommand;prismnav:生成导航服务(INavigationService)注入模板。
5.14 常见坑
- 视图/VM 必须满足「Views/ViewModels 文件夹 + 命名」约定,否则需
AutoWireViewModel。 DelegateCommand可执行状态不更新:用ObservesProperty或手动RaiseCanExecuteChanged()。- Region 导航失败:
RegionName拼写一致 + 视图已RegisterForNavigation注册。 - 事件聚合器订阅不触发:同一事件类型;UI 操作指定
ThreadOption.UIThread;避免接收方被 GC 回收。 - 对话框不弹出:确保已
RegisterDialog<T>()注册,且对应 VM 实现了IDialogAware。
六、选型建议
| 场景 | 推荐框架 | 原因 |
|---|---|---|
| 小型项目 / 快速原型 | CommunityToolkit.Mvvm | 轻量、官方维护、源生成器减少代码 |
| 遗留项目维护 | MvvmLight | 兼容存量代码(不建议新项目使用) |
| 大型 / 模块化项目 | Prism | 模块化、导航、DI 等企业级特性 |
| 跨平台(MAUI 等) | CommunityToolkit.Mvvm | 支持最好,官方推荐 |
| 强类型事件通信 | Prism / CT.Mvvm | EventAggregator / WeakReferenceMessenger |
总结
- MvvmLight:轻量、简单、易上手,但已基本停更,适合学习与遗留项目。
- CommunityToolkit.Mvvm:微软官方、轻量、高性能(编译期源生成器、弱引用消息),是新项目首选。
- Prism:企业级重型框架,解决「模块化、区域导航、DI」等复杂需求,学习成本高。
三者核心思想一致、API 高度对应(ViewModelBase↔ObservableObject↔BindableBase、RelayCommand↔DelegateCommand、Messenger↔WeakReferenceMessenger↔EventAggregator),迁移成本较低,核心逻辑可复用。