深入理解C# MVVM模式:从理论到实践

article/2025/7/12 11:41:05

在现代软件开发中,良好的架构设计对于构建可维护、可测试和可扩展的应用程序至关重要。Model-View-ViewModel (MVVM) 是一种特别适合XAML-based应用程序(如WPF、Xamarin和UWP)的架构模式。本文将全面探讨MVVM模式的概念、实现细节、最佳实践以及在实际项目中的应用。

第一部分:MVVM模式概述

1.1 什么是MVVM模式?

MVVM是一种软件架构模式,由微软架构师John Gossman于2005年专门为WPF(Windows Presentation Foundation)引入。它源自MVC(Model-View-Controller)和MVP(Model-View-Presenter)模式,但针对数据绑定机制进行了优化。

MVVM的核心思想是"关注点分离"(Separation of Concerns),将应用程序分为三个主要部分:

  • Model:代表数据和业务逻辑

  • View:用户界面展示

  • ViewModel:连接View和Model的桥梁

1.2 MVVM与其他模式的比较

特性MVCMVPMVVM
视图主动性被动被动被动
通信方向多向双向单向(通常)
数据绑定无/弱无/弱
适合平台WebWinFormsWPF/XAML

MVVM的最大特点是充分利用了XAML平台的数据绑定能力,最小化代码后台(Code-behind)中的逻辑。

第二部分:MVVM核心组件详解

2.1 Model层实现

Model代表应用程序的数据模型和业务逻辑。良好的Model设计应该:

public class User : ObservableObject
{private string _name;private string _email;public string Name{get => _name;set => SetProperty(ref _name, value);}public string Email{get => _email;set => SetProperty(ref _email, value);}public bool IsValid => !string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Email) &&Email.Contains("@");
}

最佳实践:

  1. 实现数据验证逻辑

  2. 保持与UI无关

  3. 考虑添加序列化支持

  4. 使用领域驱动设计(DDD)原则

2.2 ViewModel层实现

ViewModel是MVVM的核心,它需要:

  1. 实现属性通知机制

  2. 提供命令绑定支持

  3. 处理视图逻辑

扩展之前的ObservableObject:

public class UserViewModel : ObservableObject
{private User _user;private string _statusMessage;public UserViewModel(){_user = new User();SubmitCommand = new RelayCommand(Submit, CanSubmit);}public string Name{get => _user.Name;set{_user.Name = value;OnPropertyChanged();SubmitCommand.RaiseCanExecuteChanged();}}public string Email{get => _user.Email;set{_user.Email = value;OnPropertyChanged();SubmitCommand.RaiseCanExecuteChanged();}}public string StatusMessage{get => _statusMessage;set => SetProperty(ref _statusMessage, value);}public RelayCommand SubmitCommand { get; }private bool CanSubmit() => _user.IsValid;private void Submit(){// 模拟保存操作StatusMessage = $"用户 {Name} 保存成功!";// 实际项目中这里会调用服务层}
}

2.3 View层实现

View应该尽可能简单:

<Window x:Class="MvvmDemo.Views.UserView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="用户管理" Height="450" Width="800"><Grid Margin="10"><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions><StackPanel Grid.Row="0" Orientation="Horizontal"><TextBlock Text="姓名:" Width="100" VerticalAlignment="Center"/><TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Width="200" Margin="5"/></StackPanel><StackPanel Grid.Row="1" Orientation="Horizontal"><TextBlock Text="邮箱:" Width="100" VerticalAlignment="Center"/><TextBox Text="{Binding Email, UpdateSourceTrigger=PropertyChanged}" Width="200" Margin="5"/></StackPanel><Button Grid.Row="2" Content="提交" Command="{Binding SubmitCommand}"Width="100" Margin="5" HorizontalAlignment="Left"/><TextBlock Grid.Row="3" Text="{Binding StatusMessage}"Margin="5" Foreground="Green" FontWeight="Bold"/></Grid>
</Window>

第三部分:高级MVVM概念

3.1 依赖注入与MVVM

现代MVVM实现通常结合依赖注入(DI):

// 配置DI容器
public void ConfigureServices(IServiceCollection services)
{services.AddTransient<UserViewModel>();services.AddSingleton<IUserRepository, UserRepository>();services.AddSingleton<IDialogService, DialogService>();
}// ViewModel中使用依赖
public class UserViewModel : ObservableObject
{private readonly IUserRepository _userRepository;private readonly IDialogService _dialogService;public UserViewModel(IUserRepository userRepository, IDialogService dialogService){_userRepository = userRepository;_dialogService = dialogService;}// ...其他代码
}

3.2 导航与ViewModel定位器

实现跨ViewModel导航:

public class ViewModelLocator
{public MainViewModel Main => App.Services.GetRequiredService<MainViewModel>();public UserViewModel User => App.Services.GetRequiredService<UserViewModel>();
}// App.xaml中
<Application x:Class="MvvmDemo.App"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:MvvmDemo"><Application.Resources><local:ViewModelLocator x:Key="Locator"/></Application.Resources>
</Application>// View中使用
DataContext="{Binding Source={StaticResource Locator}, Path=User}"

3.3 事件聚合器模式

处理跨ViewModel通信:

public class EventAggregator
{private readonly Dictionary<Type, List<object>> _handlers = new();public void Subscribe<T>(Action<T> handler){if (!_handlers.ContainsKey(typeof(T)))_handlers[typeof(T)] = new List<object>();_handlers[typeof(T)].Add(handler);}public void Publish<T>(T message){if (_handlers.TryGetValue(typeof(T), out var handlers)){foreach (var handler in handlers.Cast<Action<T>>()){handler(message);}}}
}// 使用示例
_eventAggregator.Subscribe<UserUpdatedEvent>(e => RefreshUserList());

第四部分:MVVM框架比较

4.1 主流MVVM框架对比

框架特点适用场景
Prism功能全面,企业级大型复杂应用
MVVM Light轻量简单小型/中型项目
Caliburn.Micro约定优于配置快速开发
ReactiveUI响应式编程数据流复杂应用
CommunityToolkit.Mvvm官方维护,现代化API新项目,UWP/WinUI

4.2 使用Community Toolkit MVVM示例

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;[ObservableObject]
public partial class UserViewModel
{[ObservableProperty]private string _name;[ObservableProperty]private string _email;[ObservableProperty]private string _statusMessage;[RelayCommand(CanExecute = nameof(CanSubmit))]private void Submit(){StatusMessage = $"用户 {Name} 保存成功!";}private bool CanSubmit => !string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Email) &&Email.Contains("@");
}

第五部分:MVVM最佳实践

5.1 测试策略

ViewModel应该易于单元测试:

[TestClass]
public class UserViewModelTests
{[TestMethod]public void SubmitCommand_WhenUserInvalid_CannotExecute(){var vm = new UserViewModel();vm.Name = "";vm.Email = "invalid";Assert.IsFalse(vm.SubmitCommand.CanExecute(null));}[TestMethod]public void SubmitCommand_WhenUserValid_UpdatesStatus(){var vm = new UserViewModel();vm.Name = "Test";vm.Email = "test@example.com";vm.SubmitCommand.Execute(null);Assert.AreEqual("用户 Test 保存成功!", vm.StatusMessage);}
}

5.2 性能考虑

  1. 避免频繁的属性通知

  2. 对集合使用ObservableCollection

  3. 考虑使用延迟绑定或虚拟化

  4. 对复杂数据使用增量更新

5.3 常见陷阱与解决方案

问题解决方案
内存泄漏弱引用事件处理或及时取消订阅
绑定失败检查输出窗口的绑定错误
性能问题使用性能分析工具诊断
ViewModel过于庞大按功能拆分ViewModel

结语

MVVM模式虽然有一定的学习曲线,但它为XAML-based应用程序提供了清晰的结构和优秀的可维护性。通过合理应用MVVM模式,开发者可以:

  1. 创建更易于测试的代码

  2. 实现更好的团队协作

  3. 构建更易于维护的应用程序

  4. 提高开发效率

随着.NET生态系统的不断发展,MVVM模式也在不断进化,特别是随着MAUI的推出和WinUI 3的发展,MVVM仍然是构建复杂桌面和移动应用程序的首选架构模式。

 


http://www.hkcw.cn/article/xpJIUjgvwP.shtml

相关文章

Git GitHub Gitee

一、Git 是一个免费、开源的分布式版本控制系统。 版本控制&#xff1a;一种记录文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统。它最重要的就是可以记录文件修改历史记录&#xff0c;从而让用户可以看历史版本&#xff0c;方便版本切换。 1.和集中式版本控制…

数据库管理-第332期 大数据已死,那什么当立?(20250602)

数据库管理332期 2025-06-02 数据库管理-第332期 大数据已死&#xff0c;那什么当立&#xff1f;&#xff08;20250602&#xff09;1 概念还是技术2 必然的大数据量3 离线到实时4 未来总结 数据库管理-第332期 大数据已死&#xff0c;那什么当立&#xff1f;&#xff08;202506…

Java Netty 中处理粘包和半包问题的解决方案 | TCP消息完整性校验(XOR )

文章目录 引言I 处理TCP粘包和半包问题背景粘包问题的产生原因解决方案WebSocket中的粘包和半包问题及解决方案II Java Netty 中处理粘包和半包问题粘包和半包问题可以通过以下几种方式解决:使用分隔符解码器基于长度字段的解码器实现自定义解码器III TCP常见封装处理消息接收…

鸿蒙next系统以后会取代安卓吗?

点击上方关注 “终端研发部” 设为“星标”&#xff0c;和你一起掌握更多数据库知识 官方可没说过取代谁谁&#xff0c;三足鼎立不好吗&#xff1f;三分天下&#xff0c;并立共存。 鸿蒙基于Linux&#xff0c;有人说套壳&#xff1b;ios/macos基于Unix&#xff0c;说它ios开源了…

排便不是一件可以随意“延后”的事:长期便秘->直肠敏感性降低->功能性便秘->大便失禁

文章目录 引言知识扩展: 快乐排便的黄金姿势I 便秘并不是一种单一成因的疾病便秘成因临床治疗II 总是压抑排便,身体会发生的变化III 排便不是一件可以随意“延后”的事引言 排便是一种复杂的反射行为: 由“直肠充盈—产生便意—括约肌协调—排出”的完整生理链条完成的。 …

基于Spring Boot 电商书城平台系统设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…

Golang——5、函数详解、time包及日期函数

函数详解、time包及日期函数 1、函数1.1、函数定义1.2、函数参数1.3、函数返回值1.4、函数类型与变量1.5、函数作参数和返回值1.6、匿名函数、函数递归和闭包1.7、defer语句1.8、panic和recover 2、time包以及日期函数2.1、time.Now()获取当前时间2.2、Format方法格式化输出日期…

HTTP详解

使用的工具&#xff1a;fiddler 一、请求和响应报文的结构 1.请求 ⾸⾏: [⽅法] [url] [版本]Header: 请求的属性, 冒号分割的键值对;每组属性之间使⽤\n分隔;遇到空⾏表⽰Header部分结束Body: 空⾏后⾯的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有…

解决:install via Git URL失败的问题

为解决install via Git URL失败的问题&#xff0c;修改安全等级security_level的config.ini文件&#xff0c;路径如下&#xff1a; 还要重启&#xff1a; 1.reset 2.F5刷新页面 3.关机服务器&#xff0c;再开机&#xff08;你也可以省略&#xff0c;试试&#xff09; 4.Wind…

【小工具开发】通过Java实现批量修改文件名小工具

1. 创建Project&#xff08;使用Gradle&#xff09; 2.安装Gradle 修改 distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-8.12-bin.zip 修改 distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-8.12-all.zip 阅读了以下博客&#xff0c;发…

【AI智能体】Spring AI MCP 从使用到操作实战详解

目录 一、前言 二、MCP 介绍 2.1 什么是MCP 2.2 MCP 核心特点 2.3 MCP 核心价值 2.4 MCP 与Function Calling 区别 三、Spring AI MCP 架构介绍 3.1 整体架构 3.1.1 三层架构实现说明 3.2 服务端与客户端 3.2.1 MCP 服务端 3.2.1 MCP 客户端 3.3 MCP中SSE和STDIO区…

Python打卡DAY43

复习日 作业&#xff1a; kaggle找到一个图像数据集&#xff0c;用cnn网络进行训练并且用grad-cam做可视化 进阶&#xff1a;并拆分成多个文件 我选择ouIntel Image Classification | Kagglezz&#xff0c;该数据集分为六类&#xff0c;包含建筑、森林、冰川、山脉、海洋和街道…

香橙派3B学习笔记5:Linux文件系统分区_A/B系统分区

经过之前的多次分区失败导致系统重启无法进入&#xff0c;这次调整思路重新分区 ssh &#xff1a; orangepi本地ip 密码 &#xff1a; orangepi 操作系统发行版&#xff1a; 基于 Ubuntu 20.04.6 LTS&#xff08;Focal Fossa&#xff09;的定制版本&#xff0c;专门为 Orange …

第16讲、Odoo 18 序号(Sequence)详解

目录 序号系统概述底层实现原理常见序号规则使用方法与最佳实践案例分析&#xff1a;客户工单管理系统常见问题与解决方案总结 序号系统概述 Odoo 中的序号&#xff08;Sequence&#xff09;系统是一个用于生成唯一标识符的核心机制&#xff0c;主要用于为业务单据&#xff…

内存管理--《Hello C++ Wrold!》(8)--(C/C++)--深入剖析new和delete的使用和底层实现

文章目录 前言C/C内存分布new和deletenew和delete的底层定位new表达式 内存泄漏作业部分 前言 在C/C编程中&#xff0c;内存管理是理解程序运行机制的核心基础&#xff0c;也是开发高效、稳定程序的关键。无论是局部变量的存储、动态内存的分配&#xff0c;还是对象生命周期的…

Linux之进程间通信

目录 一、进程间通信介绍 1.1、进程间通信目的 1.2、进程间通信发展 1.3、进程间通讯分类 二、管道 三、匿名管道 3.1、示例代码 完整重定向问题&#xff1a; 3.2、⽤ fork 来共享管道原理 3.3、站在⽂件描述符⻆度-深度理解管道 3.4、站在内核⻆度-管道本质 3.5、…

京东轨迹验证码识别代码

一、简介 这个是最新的京东轨迹验证码&#xff0c;需要用户根据轨迹画出对应的曲线。这个和传统的验证码有较大的差异&#xff0c;有非常大的难度。经过长时间的研究&#xff0c;现在终于解决了它的识别问题。 这个是识别效果&#xff0c;和真实轨迹基本上重合&#xff0c;所以…

【Godot】如何导出 Release 版本的安卓项目

在使用 Godot 引擎开发安卓游戏或应用时&#xff0c;发布到应用市场&#xff08;如 Google Play、华为应用市场等&#xff09;通常需要生成一个 Release 版本的 .apk 包&#xff0c;而非 Debug 版本。本文将详细介绍如何将 Godot 项目导出为 Release 版本的安卓项目&#xff0c…

哈喽,我是钓鱼的肝

我嘛&#xff0c;一个五年级的小猴子&#xff0c;哦不&#xff0c;小孩子&#xff0c;给大家看看我长什么样子 呃&#xff0c;放错了 别想了&#xff0c;等你关注我再说 我写博客的缘由就是想让大家一起见证我的进步&#xff0c;二是把我的学习成果让更多人看见&#xff0c;三…

【Linux】网络--网络层--IP协议

个人主页~ 网络--网络层--IP协议 一、基本概念二、IP报头格式三、网络划分四、私有IP和公网IP五、路由路由表 六、与数据链路层之间的协议 一、基本概念 IP 协议是用于在互联网上进行数据传输的一种网络层协议&#xff0c;它为不同网络中的设备提供了一种统一的方式来交换数据…