CommunityToolkit.Mvvm 编码规范实战指南基于 MVVM Toolkit 的 ViewModel、命令、消息与校验最佳实践【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot导读本文是围绕 GitHub Copilot Agents 生态仓库中 instructions/mvvm-toolkit.instructions.md 展开的深度技术指南系统讲解CommunityToolkit.MvvmMVVM Toolkit在 WPF、WinUI 3、.NET MAUI、Uno Platform、Avalonia 五大 XAML 框架下的编码约定从[ObservableProperty]/[RelayCommand]源代码生成器、三大基类选型到IMessenger消息总线、Microsoft.Extensions.DependencyInjection依赖注入与ObservableValidator数据校验的完整闭环。读完本文你将掌握一套可直接落地的 MVVM Toolkit 8.x 编码规范并理解每条规则背后的生成器行为与坑位成因。该规范文件由仓库贡献者维护配套的技能文档位于 skills/mvvm-toolkit/SKILL.md核心技能、skills/mvvm-toolkit-messenger/SKILL.md消息总线与 skills/mvvm-toolkit-di/SKILL.md依赖注入本文所有示例均可与之一一对应。一、包与语言要求8.x 时代的前提条件规范首先明确了两条硬性前提引用CommunityToolkit.Mvvm8.x或更新版本新项目严禁安装旧版Microsoft.Toolkit.Mvvm7.x。8.x 的核心变化是源代码生成器与 NuGet 包一体化发布——生成器随包分发无需额外引用 analyzer 包同时目标框架覆盖netstandard2.0、netstandard2.1、net6.0可运行于 .NET、.NET Framework 与 Mono。C#LangVersion必须支持源代码生成器现代 SDK 默认可满足否则[ObservableProperty]等属性无法触发编译期代码生成。在 .csproj 中的标准声明方式见 skills/mvvm-toolkit/SKILL.mdItemGroup PackageReference IncludeCommunityToolkit.Mvvm Version8.* / /ItemGroup常用命名空间using CommunityToolkit.Mvvm.ComponentModel; // ObservableObject、[ObservableProperty] using CommunityToolkit.Mvvm.Input; // [RelayCommand]、RelayCommand、AsyncRelayCommand全仓库通用的铁律partial每一个使用了[ObservableProperty]或[RelayCommand]的类型——以及嵌套时的每一个外层类型——都必须声明为partial。生成器会为类型补发一个同名的兄弟 partial 声明缺失partial时编译器会报告MVVMTK0008/MVVMTK0042错误。这是上手 Toolkit 8.x 时最常见的编译期错误后文排障章节会给出完整诊断码表。二、ViewModel 基类选型三类基类各司其职规范给出了清晰的选型决策树基类适用场景提供的能力ObservableObject默认选择INotifyPropertyChangedINotifyPropertyChanging 全套SetProperty重载 SetPropertyAndNotifyOnCompletion用于Task属性ObservableValidatorViewModel 需要INotifyDataErrorInfo表单、设置页、输入校验在ObservableObject之上叠加 DataAnnotations 校验管线ObservableRecipientViewModel 需要发送或接收IMessenger消息在ObservableObject之上叠加IMessenger的自动注册/注销生命周期三条硬性规则绝不手写INotifyPropertyChanged——只要能用 Toolkit 基类就继承若类型无法继承基类例如自定义控件退而使用类级[ObservableObject]或[INotifyPropertyChanged]属性把整套生成能力烙在类型上。典型场景见 skills/mvvm-toolkit/references/source-generators.md[INotifyPropertyChanged] public partial class MyControl : UserControl { [ObservableProperty] private string? caption; }C# 是单继承ObservableValidator与ObservableRecipient都继承自ObservableObject二者无法同时继承。需要两者能力时用组合继承其一把另一个如IMessenger通过构造函数注入进来。区分能力与场景表单校验选ObservableValidator消息收发选ObservableRecipient其余一律ObservableObject——不要为了未来可能用到而过度设计。三、属性与[ObservableProperty]让生成器替你写样板代码3.1 字段命名与生成规则[ObservableProperty]作用于私有字段生成对应的公有INotifyPropertyChanged属性。字段命名只接受三种形式绝不能用 PascalCase字段name→ 属性Name字段_name→ 属性Name字段m_name→ 属性Name字段NamePascalCase→编译错误与生成的属性名冲突规范明确不要写手动的SetProperty(ref field, value)样板。当字段符合[ObservableProperty]条件时直接交给生成器。生成器产物简化如下public string? Name { get name; set { if (!EqualityComparerstring?.Default.Equals(name, value)) { string? oldValue name; OnNameChanging(value); OnNameChanging(oldValue, value); OnPropertyChanging(); name value; OnNameChanged(value); OnNameChanged(oldValue, value); OnPropertyChanged(); } } } partial void OnNameChanging(string? value); partial void OnNameChanging(string? oldValue, string? newValue); partial void OnNameChanged(string? value); partial void OnNameChanged(string? oldValue, string? newValue);3.2OnXxxChanging/OnXxxChanged部分方法钩子属性变更的副作用日志、联动、状态切换应放在生成器暴露的 partial 方法钩子中而不是订阅自己的PropertyChanged事件。钩子支持单参(value)与双参(oldValue, newValue)两种签名实现哪个写哪个未实现的钩子会被编译器裁剪零运行时开销[ObservableProperty] private string? name; partial void OnNameChanged(string? value) Logger.LogInformation(Name changed to {Name}, value);双参版本常用于需要感知旧值的场景见 skills/mvvm-toolkit/references/source-generators.md[ObservableProperty] private ChildViewModel? selectedItem; partial void OnSelectedItemChanging(ChildViewModel? oldValue, ChildViewModel? newValue) { if (oldValue is not null) oldValue.IsSelected false; if (newValue is not null) newValue.IsSelected true; }注意钩子方法声明为无主体的partial不能显式添加访问修饰符不能写public/private。3.3 依赖属性与依赖命令两个关键通知属性[NotifyPropertyChangedFor(nameof(Derived))]当本字段变更时额外为派生/计算属性引发PropertyChanged。可叠加多个属性目标[ObservableProperty] [NotifyPropertyChangedFor(nameof(FullName))] [NotifyPropertyChangedFor(nameof(Initials))] private string? firstName; public string FullName ${FirstName} {LastName}; public string Initials ${FirstName?[0]}{LastName?[0]};[NotifyCanExecuteChangedFor(nameof(XxxCommand))]当本字段变更时自动调用XxxCommand.NotifyCanExecuteChanged()让命令基于最新输入重新评估CanExecute——这是按钮启用/禁用状态与输入保持同步的关键。目标必须是同类型中可访问的IRelayCommand或IAsyncRelayCommand属性否则触发MVVMTK0016诊断。[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SaveCommand))] [NotifyCanExecuteChangedFor(nameof(SubmitCommand))] private string? name; [RelayCommand(CanExecute nameof(CanSave))] private Task SaveAsync() repo.SaveAsync(Name!); private bool CanSave() !string.IsNullOrWhiteSpace(Name);3.4 属性转发[property: SomeAttribute]需要把特性如[JsonIgnore]、[JsonPropertyName(...)]附着到生成的属性上而非字段上时使用[property: ]目标前缀[ObservableProperty] [property: JsonRequired] [property: JsonPropertyName(name)] private string? username;[RelayCommand]方法同样支持向生成的命令属性转发特性。仅当类型派生自ValidationAttribute的特性才会被自动转发到生成属性其余特性一律需要[property: ]显式指定。3.5 包裹非可观察模型当需要让普通 POCO 可观察时用SetProperty的带转换器重载包裹见 skills/mvvm-toolkit/SKILL.md并传入无捕获的静态 lambda以保持零分配public sealed class ObservableUser(User user) : ObservableObject { public string Name { get user.Name; set SetProperty(user.Name, value, user, (u, n) u.Name n); } }四、命令与[RelayCommand]从样板到一行属性4.1 生成规则与命名优先使用[RelayCommand]作用于实例方法而不是手动构造RelayCommand/AsyncRelayCommand实例——属性式写法覆盖约 95% 的场景仅在需要显式掌控命令生命周期或从非平凡来源组合命令时才回退到手动构造。生成器命名规则Refresh→RefreshCommandOnRefresh→RefreshCommand剥离前导OnLoadAsync→LoadCommand剥离尾缀AsyncOnLoadAsync→LoadCommand两者都剥离生成的命令以IRelayCommand/IAsyncRelayCommand接口形式惰性暴露方法带参数时生成强类型命令如IRelayCommandUser GreetUserCommand。方法签名约束[RelayCommand]方法必须返回void或Task或TaskT绝不要用async void——生成器只会把返回Task的方法包装为IAsyncRelayCommandasync void会被当作同步RelayCommand包装异常变成未观察异常。4.2 五种核心命令模式// 1. 同步命令 [RelayCommand] private void Refresh() Items.Reset(); // 2. 异步命令返回 Task [RelayCommand] private async Task LoadAsync() { foreach (var item in await service.GetItemsAsync()) Items.Add(item); } // 3. 可取消的异步命令CancellationToken 参数 配对取消命令 [RelayCommand(IncludeCancelCommand true)] private async Task DownloadAsync(CancellationToken token) { await using var stream await http.GetStreamAsync(url, token); // ... } // 4. 带 CanExecute 的异步命令 [RelayCommand(CanExecute nameof(CanSave))] private Task SaveAsync() repo.SaveAsync(Name!); private bool CanSave() !string.IsNullOrWhiteSpace(Name); // 5. 允许并发执行 [RelayCommand(AllowConcurrentExecutions true)] private async Task PingAsync(string host) { /* fire-and-keep-going */ }关键行为细节见 skills/mvvm-toolkit/references/source-generators.md 与 skills/mvvm-toolkit/references/relaycommand-cookbook.mdIncludeCancelCommand true额外生成配对的XxxCancelCommand其CanExecute自动绑定到底层异步命令的IsRunning状态——把它绑到取消按钮即可在运行时启用/禁用。取消通过向方法注入的CancellationToken传播调用XxxCommand.Cancel()即发出取消信号。AllowConcurrentExecutions默认false——前一次执行未完成时命令报告自身不可执行。仅在重叠调用明确安全且有意为之时才设为true。当方法带CancellationToken且不允许并发时运行中再次请求执行会先取消前一次任务的 token。FlowExceptionsToTaskScheduler默认错误策略是 await-and-rethrow异常在同步上下文重新抛出镜像同步命令的行为——未捕获异常会崩溃应用。仅当 UI 绑定ExecutionTask渲染错误状态时才设为true此时异常改经ExecutionTask与TaskScheduler.UnobservedTaskException通道暴露。4.3 异步命令状态绑定IAsyncRelayCommand暴露一组可直接绑定到 UI 的状态属性属性类型用途ExecutionTaskTask?当前正在运行或最近完成的任务IsRunningbool任务进行中为trueCanBeCanceledbool包装方法是否带CancellationTokenIsCancellationRequestedbool对运行中任务调用Cancel()后为trueXAML 中绑定进度与错误状态而不阻塞 UI 线程StackPanel ProgressRing IsActive{x:Bind ViewModel.SyncCommand.IsRunning, ModeOneWay}/ TextBlock Text{x:Bind ViewModel.SyncCommand.ExecutionTask.Status, ModeOneWay}/ TextBlock Text{x:Bind ViewModel.SyncCommand.ExecutionTask.Exception, ModeOneWay}/ /StackPanel4.4 手动构造命令什么时候退而求其次当需要由多个方法组合、动态重建、或基于外部可观察对象构造CanExecute谓词时使用手动构造public sealed class CounterViewModel : ObservableObject { public CounterViewModel() { IncrementCommand new RelayCommand(() Counter); DecrementCommand new RelayCommand(() Counter--, () Counter 0); } [ObservableProperty] private int counter; public IRelayCommand IncrementCommand { get; } public IRelayCommand DecrementCommand { get; } }需要手动触发CanExecute重评估时调用SomeCommand.NotifyCanExecuteChanged()。五、消息总线IMessenger的注册、发送与生命周期5.1 实现选型实现适用时机WeakReferenceMessenger.Default默认。接收方被弱引用持有——即使已注册也可被 GC 回收内部在完整 GC 期间执行修剪无需手动Cleanup()StrongReferenceMessenger.Default仅当性能分析显示 messenger 是热点且分配开销成为问题时。接收方被强引用钉住直到显式Unregister忘记注销会泄漏自定义IMessenger实例按窗口/按作用域隔离如每个应用窗口一个 messenger直接构造后经 DI 注入规范的态度很明确默认WeakReferenceMessenger.Default只有 profile 证明 messenger 是热点才切换强引用版本并且必须书面记录生命周期保证。5.2 注册与发送优先使用(recipient, message)lambda 形式注册并加static修饰符——lambda 内绝不捕获this闭包分配 生命周期混乱WeakReferenceMessenger.Default.RegisterMyViewModel, ThemeChangedMessage( this, static (recipient, message) recipient.OnThemeChanged(message.NewTheme));更推荐在ObservableRecipient上实现IRecipientTMessage接口让RegisterAll(this)在IsActive true时自动接线public sealed class MyViewModel : ObservableRecipient, IRecipientThemeChangedMessage, IRecipientRefreshRequestedMessage { public void Receive(ThemeChangedMessage message) { /* ... */ } public void Receive(RefreshRequestedMessage message) { /* ... */ } }发送侧WeakReferenceMessenger.Default.Send(new ThemeChangedMessage(AppTheme.Dark)); // 无载荷消息使用无参重载 WeakReferenceMessenger.Default.SendRefreshRequestedMessage();消息类型定义方面Toolkit 自带基类如ValueChangedMessageT普通 record 类同样可用using CommunityToolkit.Mvvm.Messaging.Messages; public sealed class LoggedInUserChangedMessage(User user) : ValueChangedMessageUser(user); public sealed record ThemeChangedMessage(AppTheme NewTheme); public sealed record RefreshRequestedMessage;5.3 激活生命周期IsActiveObservableRecipient的OnActivated当IsActive从false翻转为true时触发自动执行Messenger.RegisterAll(this)OnDeactivated翻回false自动注销。规范要求在激活点设置IsActive true如OnNavigatedTo在停用点设置IsActive false如OnNavigatedFromprotected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); ViewModel.IsActive true; } protected override void OnNavigatedFrom(NavigationEventArgs e) { ViewModel.IsActive false; base.OnNavigatedFrom(e); }5.4 通道Token与请求/应答通道 token当多个消费方会冲突时用int/string/Guid重载把消息限定到子系统或窗口。注意无 token 发送的消息走默认共享通道不会投递给通道限定的接收方。const int LeftPaneChannel 1; WeakReferenceMessenger.Default.RegisterMyViewModel, RefreshRequestedMessage, int( this, LeftPaneChannel, static (r, _) r.RefreshLeft()); WeakReferenceMessenger.Default.Send(new RefreshRequestedMessage(), LeftPaneChannel);继承不参与消息投递——必须为每个具体消息类型显式注册注册BaseMessage的处理器不会被DerivedMessage触发。请求/应答RequestMessageT同步、AsyncRequestMessageT异步、CollectionRequestMessageT/AsyncCollectionRequestMessageTfan-in 收集所有应答用于问一个值的场景public sealed class CurrentUserRequest : RequestMessageUser { } WeakReferenceMessenger.Default.RegisterUserService, CurrentUserRequest( this, static (r, m) m.Reply(r.CurrentUser)); var request WeakReferenceMessenger.Default.SendCurrentUserRequest(); if (request.HasReceivedResponse) User user request.Response;更多通道、生命周期与诊断细节见 skills/mvvm-toolkit-messenger/SKILL.md。六、依赖注入MVVM Toolkit 与 DI 的协作模式MVVM Toolkit刻意不内置 DI 容器与Microsoft.Extensions.DependencyInjectionASP.NET Core、Worker 服务、.NET Generic Host 同款容器组合使用。6.1 组合根首选 .NET Generic Host在组合根通常是App.xaml.cs中构建服务提供者规范优先推荐Host.CreateDefaultBuilder()——自动接入配置appsettings.json、日志Microsoft.Extensions.Logging、托管服务IHostedService与开发环境的 scope 验证public partial class App : Application { public IHost Host { get; } public App() { Host Microsoft.Extensions.Hosting.Host .CreateDefaultBuilder() .ConfigureServices((_, services) { services.AddSingletonIFilesService, FilesService(); services.AddSingletonISettingsService, SettingsService(); services.AddSingletonIMessenger(WeakReferenceMessenger.Default); services.AddSingletonShellViewModel(); services.AddTransientContactViewModel(); services.AddTransientEditorViewModel(); }) .Build(); } public static T GetServiceT() where T : class ((App)Current).Host.Services.GetRequiredServiceT(); }页面级 ViewModel 在页面构造函数中从 DI 解析或交给导航框架解析禁止在 ViewModel、服务以及任何 DI 容器能构造的类型内部调用Ioc.Default.GetServiceT()——那会隐藏依赖并破坏单元测试。6.2 生命周期选型生命周期方法XAML 应用中的典型用途SingletonAddSingletonTShell/主窗口 VM、设置、文件/HTTP 服务、共享IMessenger、应用级缓存TransientAddTransientT按页/按文档的 VM每次解析都是新实例ScopedAddScopedT客户端应用很少需要仅在显式使用IServiceScope时如按窗口作用域6.3IMessenger的单次注册IMessenger只注册一次然后经ObservableRecipient(messenger)构造函数注入services.AddSingletonIMessenger(WeakReferenceMessenger.Default); // 或 services.AddSingletonIMessenger(StrongReferenceMessenger.Default); public sealed partial class MyViewModel(IMessenger messenger) : ObservableRecipient(messenger) { }多窗口桌面应用可按窗口注册 scoped messenger 隔离广播.NET 8还支持 keyed services 按键解析同一接口的不同实现详见 skills/mvvm-toolkit-di/SKILL.md。6.4 构造函数注入与测试缝隙依赖经构造函数显式注入使单测可直插 fake/mock也让容器在启动时即可验证依赖图。遗留的Ioc.Default服务定位器仅是无法构造函数注入场景设计期数据、ValueConverter、控件模板的最后逃生通道。七、数据校验ObservableValidator全流程7.1 快速上手ObservableValidator在ObservableObject之上叠加INotifyDataErrorInfo与System.ComponentModel.DataAnnotations校验特性集成。[NotifyDataErrorInfo]让生成的 setter 在每次成功赋值后调用ValidateProperty(value)实现边输入边校验using System.ComponentModel.DataAnnotations; public sealed partial class RegistrationViewModel : ObservableValidator { [ObservableProperty] [NotifyDataErrorInfo] [Required] [MinLength(2), MaxLength(100)] private string? name; [ObservableProperty] [NotifyDataErrorInfo] [Required, EmailAddress] private string? email; [ObservableProperty] [NotifyDataErrorInfo] [Range(13, 120)] private int age; [RelayCommand] private void Submit() { ValidateAllProperties(); if (HasErrors) return; // submit... } }7.2 核心 API 面ValidateAllProperties()提交前强制校验所有带校验特性的公有属性配合HasErrors短路退出。ClearAllErrors()成功提交或重置表单后清除错误状态。ValidateProperty(value, propertyName)手动触发单属性校验——跨属性规则属性 A 的校验依赖属性 B时在 B 的OnBChanged钩子里重跑 A 的校验[Range(10, 100)] [GreaterThan(nameof(B))] [ObservableProperty] private int a; [Range(20, 80)] [ObservableProperty] private int b; partial void OnBChanged(int value) { ValidateProperty(A, nameof(A)); }TrySetProperty仅在校验通过时设置属性GetErrors(propertyName)/GetErrors()/HasErrors用于读取错误ErrorsChanged事件或重写OnErrorsChanged用于订阅变化。手动手写属性时SetProperty(ref field, value, validate: true)显式启用校验。7.3 自定义规则两种方式详见 skills/mvvm-toolkit/references/validation.md[CustomValidation]静态方法方法必须是static签名(value, ValidationContext)经context.ObjectInstance回访 ViewModel适合用户名是否已被占用这类依赖服务的校验。自定义ValidationAttribute子类可复用的规则重写IsValid(value, ctx)通过ctx.ObjectInstance反射读取关联属性。注意ObservableValidator与ObservableRecipient无法同时继承——需要消息时注入IMessenger直接Send。八、XAML 绑定约定WinUI 3 / UWP 优先{x:Bind}编译绑定而非{Binding}。{x:Bind}默认OneTime因此必须显式设置ModeOneWay或ModeTwoWay。命令直接绑定生成的命令属性Command{x:Bind ViewModel.SaveCommand}。异步命令状态绑定把IsRunning、ExecutionTask.Status、ExecutionTask.Exception绑到进度环与错误文本上用于呈现进度/错误而不是阻塞 UI 线程Button Command{x:Bind ViewModel.DownloadCommand} ContentDownload/ Button Command{x:Bind ViewModel.DownloadCancelCommand} ContentCancel/ ProgressRing IsActive{x:Bind ViewModel.LoadCommand.IsRunning, ModeOneWay}/九、必须规避的反模式清单规范给出了一个禁止事项清单逐条对应着真实踩坑现场[ObservableProperty] private string Name;PascalCase 字段——与生成属性冲突编译失败。用 lowerCamel。手动RaisePropertyChanged(nameof(X))与[ObservableProperty]混用——产生重复通知。ViewModel 构造函数里调用Ioc.Default.GetServiceT()——隐藏依赖、破坏单测、绕过启动期依赖图验证。StrongReferenceMessenger不注销——接收方被钉住导致泄漏配OnDeactivated/UnregisterAll。messenger lambda 捕获this——闭包分配与生命周期混乱一律(r, m) r.OnX(m)static。[RelayCommand]方法写成async void——被当作同步命令包装异常未观察返回Task。就地变更[ObservableProperty]字段持有的同一引用——生成器用EqualityComparerT.Default判等相同引用返回true不触发通知替换实例而不是修改实例。同时继承ObservableValidator与ObservableRecipient——不可能用组合。十、端到端示例双窗格 Notes 应用仓库的 skills/mvvm-toolkit/references/end-to-end-walkthrough.md 提供了一个完整可运行的 WinUI 3 Notes 应用项目分MyApp应用壳、MyApp.Shared无 UI 类库、MyApp.Tests单测工程把前面所有要素串成一条主线。其核心骨架public sealed partial class NoteViewModel(INotesService notes, IMessenger messenger) : ObservableRecipient(messenger) { [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SaveCommand))] [NotifyCanExecuteChangedFor(nameof(DeleteCommand))] private string? filename; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SaveCommand))] private string? text; [RelayCommand(CanExecute nameof(CanSave))] private Task SaveAsync() { Messenger.Send(new NoteSavedMessage(Filename!)); return notes.SaveAsync(Filename!, Text!); } [RelayCommand(CanExecute nameof(CanDelete))] private Task DeleteAsync() notes.DeleteAsync(Filename!); private bool CanSave() !string.IsNullOrWhiteSpace(Filename) !string.IsNullOrEmpty(Text); private bool CanDelete() !string.IsNullOrWhiteSpace(Filename); }该示例值得内化的五条要点ViewModel 放进无 UI 依赖的类库——Toolkit 仅依赖netstandard2.0VM 无需 UI 宿主即可单测。构造函数注入贯穿始终——组合根知道如何构建一切VM 与服务经参数接收依赖。IMessenger是跨 VM 的粘合剂——WeakReferenceMessenger.Default是正确默认列表 VM 用IRecipientT监听编辑器 VM 用Messenger.Send发布。[NotifyCanExecuteChangedFor]让 Save/Delete 按钮与文本输入自动同步——无需手动接线。ObservableRecipient.IsActive控制订阅生命周期——在OnNavigatedTo/OnNavigatedFrom中切换。同一模式在 WPF、MAUI、Uno、Avalonia 下同样成立仅 XAML、导航与 App 引导有差异。十一、排障速查MVVMTK0xxx诊断码生成器输出编号诊断最常见的一张表完整表见 skills/mvvm-toolkit/references/troubleshooting.md代码含义修复MVVMTK0008包含类型或外层类型不是partial给类声明及每个外层类型加partialMVVMTK0016[NotifyCanExecuteChangedFor]目标不是同类型中可访问的IRelayCommand属性确认目标为[RelayCommand]生成命令或手动声明的IRelayCommand属性MVVMTK0017[NotifyDataErrorInfo]用在ObservableValidator之外继承ObservableValidator或移除特性MVVMTK0018[NotifyPropertyChangedRecipients]用在ObservableRecipient之外继承ObservableRecipient或移除特性MVVMTK0030[ObservableProperty]用于未实现INotifyPropertyChanged的类型继承ObservableObject或加类级[INotifyPropertyChanged]/[ObservableObject]MVVMTK0042泛型类型中的[ObservableProperty]字段缺少partial与MVVMTK0008相同加partial高频运行时问题的定位要点Setter 从不触发PropertyChanged同一引用赋值比较器返回true、赋值相同值设计如此、或值类型的默认相等不正确——后者手动手写属性并传comparer。取消似乎无效确认方法声明了CancellationToken参数、把 token 下传到HttpClient.GetAsync(url, token)等被等待 API、并捕获OperationCanceledException防止 UI 显示错误。messenger 处理器从不触发检查消息类型是否精确匹配继承不参与投递、发送与注册是否用同一IMessenger实例、token 是否匹配、弱引用接收方是否已被 GC单例 VM 由 DI 容器持有、以及ObservableRecipient的IsActive是否为trueOnActivated才会注册处理器。DI 无法构造 VMInvalidOperationException: Unable to resolve service for type X...——通常是构造函数参数类型未注册、多个歧义构造函数容器选依赖全部可解析的最长构造函数两个同时符合则抛异常或 scoped 服务被注入单例开发模式开启 scope 验证时会暴露。结语把规范变成肌肉记忆本文覆盖的规范文件 instructions/mvvm-toolkit.instructions.md 是仓库贡献者为跨框架WPF、WinUI 3、.NET MAUI、Uno、Avalonia团队沉淀的编码约定其深层逻辑始终围绕三条主线让源代码生成器替你消灭样板[ObservableProperty]/[RelayCommand]、用显式依赖替代隐式服务定位构造函数注入 Generic Host 组合根、用默认安全策略规避泄漏与异常陷阱弱引用 messenger、partial铁律、Task而非async void。遵循这套约定你的 ViewModel 层将同时获得可测试性、可维护性与可审查性——这正是 MVVM Toolkit 8.x 设计意图的完整落地。想深入每一处的生成代码与边界行为可直接查阅 skills/mvvm-toolkit/references/source-generators.md、skills/mvvm-toolkit/references/relaycommand-cookbook.md 与 skills/mvvm-toolkit/references/validation.md 三份参考手册。【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考