利用 SourceGenerator 和分部属性实现自动通知。

直接上代码,新建一个 c# 的 dll 项目,因为 SourceGenerator 只能是 netstandard2.0,我们先修改项目配置文件,并加上相关的引用,结果如下:

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFramework>netstandard2.0</TargetFramework>
		<LangVersion>preview</LangVersion>
		<Nullable>enable</Nullable>
	</PropertyGroup>

	<ItemGroup>
		<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
		<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
	</ItemGroup>

</Project>

添加 TestSourceGenerator.cs 文件,内容如下:

using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Diagnostics;

namespace TestGenerator;

public class TestSyntaxReceiver : ISyntaxReceiver
{
    public readonly List<ClassDeclarationSyntax> interfaceSyntaxList = [];

    void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode)
    {
        if (syntaxNode is ClassDeclarationSyntax syntax)
        {
            this.interfaceSyntaxList.Add(syntax);
        }
    }
}

[Generator]
public class TestSourceGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // 此行去掉注释可调试代码
        // Debugger.Launch();
        context.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver());
    }

    public void Execute(GeneratorExecutionContext context)
    {
        if (context.SyntaxReceiver is not TestSyntaxReceiver receiver)
        {
            return;
        }

        // 这里可以根据筛选出来的节点,在下面添加相应的代码段。
        // var syntaxNodes = receiver.interfaceSyntaxList;

        // 本例不解析,直接添加通知代码。
        context.AddSource("MainWindowViewModel.Notify.cs", """
            using GalaSoft.MvvmLight.Command;
            using System;
            using System.Collections.Generic;
            using System.ComponentModel;
            using System.Linq;
            using System.Text;
            using System.Threading;
            using System.Threading.Tasks;
            using System.Windows;
            using System.Windows.Input;

            namespace WpfApp1;

            public partial class MainWindowViewModel : INotifyPropertyChanged
            {
                private string _name = string.Empty;

                public partial string Name
                {
                    get { return _name; }
                    set
                    {
                        _name = value;
                        OnPropertyChanged(nameof(Name));
                    }
                }

                private int _age;

                public partial int Age
                {
                    get { return _age; }
                    set
                    {
                        _age = value; OnPropertyChanged(nameof(Age));
                    }
                }


                public event PropertyChangedEventHandler? PropertyChanged;

                public void OnPropertyChanged(string propertyName)
                {
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                }
            }

            """);
    }
}

接下来,我们新建一个wpf 项目,修改项目配置文件为:

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<OutputType>WinExe</OutputType>
		<TargetFramework>net5.0-windows</TargetFramework>
		<LangVersion>preview</LangVersion>
		<Nullable>enable</Nullable>
		<UseWPF>true</UseWPF>
	</PropertyGroup>

	<ItemGroup>
		<PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
	</ItemGroup>

	<ItemGroup>
		<ProjectReference Include="..\TestGenerator\TestGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
	</ItemGroup>

</Project>

主要是为了引用刚刚新建的dll项目。

接下来新建一个viewModel文件,文件中使用 partial 属性。

using GalaSoft.MvvmLight.Command;
using System;
using System.Windows.Input;

namespace WpfApp1;

public partial class MainWindowViewModel
{
    private RelayCommand? _changeCommand;

    public partial string Name { get; set; }

    public partial int Age { get; set; }

    public ICommand ChangeCommand => _changeCommand ??= new RelayCommand(ChangeName);

    private void ChangeName()
    {
        Name = Guid.NewGuid().ToString();
    }
}

然后修改MainWindow.xaml:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        x:Name="win"
        d:DataContext="{d:DesignInstance local:MainWindowViewModel}"
        Title="MainWindow"
        Height="450"
        Width="800">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <TextBlock Text="{Binding Name}" />
        <Button Content="修改"
                Command="{Binding ChangeCommand}" />
    </StackPanel>
</Window>

在MainWindow.xaml.cs中绑定ViewModel:

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainWindowViewModel();
      
    }
}

ok,编译 wpf项目。

image

可以看到,自动生成了一个 MainWindowViewModel.Notify.cs 文件。

运行项目,点击修改按钮,名称已经可以自动通知了。

image

这样的话,我们不需要在ViewModel中写相应的通知代码,只需要引用此代码生成器项目,便可实现自动通知了。这与CommunityTookit 项目的思路有相似之处。

如果在生成器中加入我们定制化的逻辑,比如查找所有partial property ,给它们都加上通知,这样的话,我们完全不用考虑通知了,而且,ViewModel也不需要实现INotifyPropertyChanged 接口。

自c# 13开始,已经支持分部属性了。如果你发现代码报错了,可以先更新一下vs版本试试。

更多思路,要看道友自行发挥了。

© 版权声明
THE END
喜欢就支持一下吧
点赞17 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情

    暂无评论内容