关于 MVVM Toolkit

最近 .NET Community Toolkit 发布了 8.0.0 preview1,它包含了从 Windows Community Toolkit 迁移过来的以下组件:

CommunityToolkit.Common

CommunityToolkit.Mvvm

CommunityToolkit.Diagnostics

CommunityToolkit.HighPerformance

其中 CommunityToolkit.Mvvm 又名 MVVM Toolkit ,它是一个现代化、快速以及模块化的 MVVM 库。Nuget 安装脚本为:

Install-Package CommunityToolkit.Mvvm -Version 8.0.0-preview1

MVVM Toolkit source generators

Source Generators 是一项 C# 编译器功能,使 C# 开发人员能够在编译用户代码时进行检查,并动态生成新的 C# 源文件,以添加到用户的编译中。 通过这种方式,你的代码可以在编译过程中运行并检查你的程序以生成与其余代码一起编译的其他源文件。

新版本的 MVVM Toolkit 包含一个全新的 source generators,现在它是一个增量生成器,性能将会提升很多。这篇文章将简单介绍一下它的功能。

命令

在 MVVM 模式中,命令的写法让人有点烦恼。这是 MVVM Toolkit 中的通常写法:

private IRelayCommand _displayCommand;

IRelayCommand DisplayCommand => _displayCommand ??= new RelayCommand(new Action(Display), () => HasName);

private void Display()
{ }

首先,代码就不少。另外,_displayCommandDisplayCommandDisplay() 是写在一起好呢,还是按字段、属性、函数的排序分别放在代码里的不同位置呢?又或者索性用 Partial 类分别放在不同的文件?

用 source generators 就没这些烦恼了,命令的定义可以简化成这样:

[ICommand(CanExecute = nameof(HasName))]
private void Display()
{
}

通过添加 ICommandAttribute,source generators 可以根据 Display() 这个函数名正确地生成 DisplayCommand 及对应的初始化代码。此外,还可以通过它的 CanExecute 属性指定将 ICommand 的 CanExecute 关联到对应的属性。

属性

属性也有和命令一样的烦恼,通常来说 MVVM 模式中的属性的写法如下:

private string name;

public string Name
{
get => name;
set => SetProperty(ref name, value);
}

其实还好,不会太多。但如果是这样呢:

 private string _surname;

 public string Surname
{
get
{
return _surname;
}
set
{
if (!EqualityComparer<string>.Default.Equals(_surnam
{
_surname = value;
OnPropertyChanged();
OnPropertyChanged(nameof(FullName));
OnPropertyChanged(nameof(HasName));
DisplayCommand.NotifyCanExecuteChanged();
}
}
} public string FullName => $"{Name} {Surname}"; public bool HasName => !string.IsNullOrWhiteSpace(FullName);

这时候 source generators 的作用就可以很明显,因为它只需要下面的代码就可以自动产生与上面等价的代码:

[ObservableProperty]
[AlsoNotifyChangeFor(nameof(FullName), nameof(HasName))]
[AlsoNotifyCanExecuteFor(nameof(DisplayCommand))]
private string _surname; public string FullName => $"{Name} {Surname}"; public bool HasName => !string.IsNullOrWhiteSpace(FullName);

从这段代码可以看到有三个 Attribute 起了作用:

ObservableProperty:自动为 _name 属性生成对应的属性。

AlsoNotifyChangeFor:属性值修改时同时触发 FullNameHasName 这两个属性的 PropertyChanged 事件。

AlsoNotifyCanExecuteFor:属性值修改时同时通知 DisplayCommand 执行它的 NotifyCanExecuteChanged()

注入到现有类

一般来说,MVVM Toolkit source generators 需要在 ObservableObject 的派生类中使用,例如:

public partial class TestModel: ObservableObject

但如果你的类已经继承了其它类,MVVM Toolk source generators 也允许你使用它的功能,方法是添加上 INotifyPropertyChangedAttribute,代码如下:

[INotifyPropertyChanged]
public partial class TestModel: Behaviour

INotifyPropertyChangedAttribute 会自动生成实现 INotifyPropertyChanged 的代码,而无需更改基类。不过遗憾的是,INotifyPropertyChangedAttribute 目前只能在未实现 INotifyPropertyChanged 接口的类中使用,即下面这种代码不能编译通过:

[INotifyPropertyChanged]
public partial class TestModel: ObservableObject

成果

使用了 source generators 可以大幅减少代码,下面这图直观展示了减少的代码量。

如果需要查看自动生成的代码,可以在分析器的 CommunityToolkit.Mvvm.SourceGenerators 节点里找到:

一些小问题

MVVM Toolkit source generators 可以重构你的代码,但代价是什么?

首先,部分功能需要 C# 8.0 以上,所以编译时可能会看到这条错误:

The source generator features from the MVVM Toolkit require consuming projects to set the C# language version to at least C# 8.0

解决方法是在项目文件的 PropertyGroup 节点里添加这段指明 C# 的版本:

<LangVersion>9.0</LangVersion>

另外,MVVM Toolkit source generators 还需要 Visual Studio 2022 才可以使用。

还有一点,我还没找到为生成的属性添加注释的方法,这对一些难以理解的属性来说十分致命,只好用回传统方法来处理这种属性。

最后,没有 CodeLens,没法直观看到属性的引用、修改等信息,用起来不是很顺手。

最后

从上面的例子来看,无论从代码量、可维护性、可阅读性来看,source generators 都有巨大的优势,但在现阶段,MVVM Toolkit source generators 用起来还是有不少小问题,不能完全代替原生写法。不过这是个很符合 80/20 原则的工具:它可以让用户用 20% 的投入解决了 80% 的问题。

其它更多的内容,请参考 Github 或其它文档:

https://github.com/CommunityToolkit/dotnet

https://github.com/CommunityToolkit/dotnet/releases/tag/v8.0.0-preview1

https://docs.microsoft.com/zh-cn/windows/communitytoolkit/mvvm/introduction

使用 MVVM Toolkit Source Generators的更多相关文章

  1. 基于 Source Generators 做个 AOP 静态编织小实验

    0. 前言 上接:用 Roslyn 做个 JIT 的 AOP 作为第二篇,我们基于Source Generators做个AOP静态编织小实验. 内容安排如下: source generators 是什 ...

  2. .NET初探源代码生成(Source Generators)

    前言 Source Generators顾名思义代码生成器,可进行创建编译时代码,也就是所谓的编译时元编程,这可让一些运行时映射的代码改为编译时,同样也加快了速度,我们可避免那种昂贵的开销,这是有价值 ...

  3. [WPF] 使用 MVVM Toolkit 构建 MVVM 程序

    1. 什么是 MVVM Toolkit 模型-视图-视图模型 (MVVM) 是用于解耦 UI 代码和非 UI 代码的 UI 体系结构设计模式. 借助 MVVM,可以在 XAML 中以声明方式定义 UI ...

  4. A Complete List of .NET Open Source Developer Projects

    http://scottge.net/2015/07/08/a-complete-list-of-net-open-source-developer-projects/?utm_source=tuic ...

  5. WPF MVVM使用prism4.1搭建

    WPF MVVM使用prism4.1搭建 MVVM即Model-View-ViewModel,MVVM模式与MVP(Model-View-Presenter)模式相似,主要目的是分离视图(View)和 ...

  6. Windows Community Toolkit 4.0 - DataGrid - Part03

    概述 在上面一篇 Windows Community Toolkit 4.0 - DataGrid - Part02 中,我们针对 DataGrid 控件的 Utilities 部分做了详细分享.而在 ...

  7. Windows Community Toolkit 4.0 - DataGrid - Part02

    概述 在上面一篇 Windows Community Toolkit 4.0 - DataGrid - Part01 中,我们针对 DataGrid 控件的 CollectionView 部分做了详细 ...

  8. Windows Community Toolkit 4.0 - DataGrid - Part01

    概述 在上面一篇 Windows Community Toolkit 4.0 - DataGrid - Overview 中,我们对 DataGrid 控件做了一个概览的介绍,今天开始我们会做进一步的 ...

  9. Windows Community Toolkit 4.0 - DataGrid - Overview

    概述 Windows Community Toolkit 4.0 于 2018 月 8 月初发布:Windows Community Toolkit 4.0 Release Note. 4.0 版本相 ...

随机推荐

  1. 【九度OJ】题目1118:数制转换 解题报告

    [九度OJ]题目1118:数制转换 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1118 题目描述: 求任意两个不同进制非 ...

  2. ZOJ 3960:What Kind of Friends Are You?

    What Kind of Friends Are You? Time Limit: 1 Second Memory Limit: 65536 KB Japari Park is a large zoo ...

  3. 为什么加密后的数据往往都是base64输出而不是hex16进制输出

    通常加密后的数据都是字节数组,比如流行的aes128对称加密,还有Rsa非对称加密,加密后得到了一个字节数组,这个字节数组存在内存中,往往我们需要输出得到我们人眼能看到的字符. 加密aes(xxx) ...

  4. CS5211替代兼容PS8625|普瑞PS8625替代方案|CapstoneCS5211

    PS8625是一个DP显示端口 到LVDS转换器芯片,利用GPU和显示端口(DP) 或嵌入式显示端口(eDP) 输出和接受LVDS输入的显示面板.PS8625实现双通道DP输入,双链路LVDS输出.P ...

  5. 【优雅代码】03-optional杜绝空指针异常

    [优雅代码]03-optional杜绝空指针异常 欢迎关注b站账号/公众号[六边形战士夏宁],一个要把各项指标拉满的男人.该文章已在github目录收录. 屏幕前的大帅比和大漂亮如果有帮助到你的话请顺 ...

  6. 如何通过Navicat远程访问宝塔面板安装的MySQL数据库

    运行环境描述 阿里云ECS 系统:CentOS Linux 7.4.1708 (Core)宝塔面板: 6.9.0数据库:MySQL 5.7.19 Navicat 远程连接 Navicat报错信息: 错 ...

  7. 编写Java程序,中国道教中掌管天宫的最高权力统治者是玉帝(Emperor),我们可以认为玉帝是一个单例模式,在这个场景中,有玉帝和天宫的大臣(Minister)们,大臣每天要上朝参见玉帝,而每一天参

    查看本章节 查看作业目录 需求说明: 中国道教中掌管天宫的最高权力统治者是玉帝(Emperor),我们可以认为玉帝是一个单例模式,在这个场景中,有玉帝和天宫的大臣(Minister)们,大臣每天要上朝 ...

  8. Java面向对象笔记 • 【第4章 抽象类和接口】

    全部章节   >>>> 本章目录 4.1 抽象类 4.1.1 抽象方法和抽象类 4.1.2 抽象类的作用 4.1.3 实践练习 4.2 final修饰符 4.2.1 final ...

  9. C#中的记录(record)

    从C#9.0开始,我们有了一个有趣的语法糖:记录(record) 为什么提供记录? 开发过程中,我们往往会创建一些简单的实体,它们仅仅拥有一些简单的属性,可能还有几个简单的方法,比如DTO等等,但是这 ...

  10. 初识python 之 ImportError: No module named _ssl

    场景 安装好python之后,导入ssl模块报错: ImportError: No module named _ssl 解决方法 查看openssl.openssl-devel是否安装 rpm -qa ...