本文告诉大家如何在使用 IIncrementalGenerator 进行增量的 Source Generator 生成代码时,读取项目里的项目文件属性,从而实现为项目定制的逻辑。或者是读取 NuGet 包里面的一些配置,从而方便实现逻辑

使用增量的源代码生成具有更高的门槛。本文属于入门博客,但非编程新手友好,期望阅读本文之前,已了解源代码生成和项目构建和项目组织的基础知识

阅读本文,你可以了解到如何在进行增量的源代码生成过程中,读取项目文件里面的属性,从而执行特殊的逻辑

本文的例子期望达成的是,读取 csproj 项目文件里面的 MyCustomProperty 属性,将此属性的文本内容,作为生成代码的一部分。以下代码是 MyCustomProperty 属性的定义。值得一说的是,此方法不仅仅适合用在读取 csproj 项目文件里面的属性,也适合用来读取 NuGet 包的 xx.props 和 xx.targets 文件里面的属性

  <PropertyGroup>
<MyCustomProperty>lindexi is doubi</MyCustomProperty>
</PropertyGroup>

在例子代码里面,期望能够将 MyCustomProperty 属性的内容,作为控制台输出的参数,输出到。相当于将 MyCustomProperty 属性的内容,放入到下面代码的 text 变量里面,加入到源代码生成

                    var code = @"using System;
namespace LainewihereJerejawwerye
{
public static class Foo
{
public static void F1()
{
Console.WriteLine(""" + text + @""");
}
}
}";

接下来是开始写这个例子的代码,本文的所有代码都可以在本文末尾找到下载地址

开始之前,按照惯例,先新建两个项目,分别是 LainewihereJerejawwerye 和 LainewihereJerejawwerye.Analyzers 两个项目。其中 LainewihereJerejawwerye 用来安装使用分析器的项目,提供 MyCustomProperty 属性。在 LainewihereJerejawwerye.Analyzers 里面,作为分析器项目,将实现源代码生成逻辑

编辑 LainewihereJerejawwerye.Analyzers 的 csproj 项目文件,替换为以下代码。下面代码的细节请参阅 使用 Source Generator 在编译你的 .NET 项目时自动生成代码 - walterlv 博客

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

  <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup> <ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
</ItemGroup> </Project>

接着编辑 LainewihereJerejawwerye 项目的 csproj 项目文件,让他引用上分析器项目。额外的加上 MyCustomProperty 属性,修改之后的代码如下

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

  <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup> <PropertyGroup>
<MyCustomProperty>lindexi is doubi</MyCustomProperty>
</PropertyGroup> <ItemGroup>
<ProjectReference Include="..\LainewihereJerejawwerye.Analyzers\LainewihereJerejawwerye.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup> </Project>

根据官方的 Source Generators Cookbook 文档,想要让分析器项目能够拿到 csproj 项目文件里面的属性,就需要明确使用 CompilerVisibleProperty 包含其对分析器可见的属性名。在属性系统里面,可以分为全局属性以及单项属性。所谓全局属性,就是对整个项目可用,而不是对项目里的某个文件进行设置的属性。单项属性就是对单个项,如单个文件进行设置的额外的配置属性。本文这里只讨论全局的属性配置情况,也就是对整个项目的配置的属性

如上文描述,添加一个 CompilerVisibleProperty 包含对分析器可见的 MyCustomProperty 属性,代码如下

  <ItemGroup>
<CompilerVisibleProperty Include="MyCustomProperty" />
</ItemGroup>

加上 CompilerVisibleProperty 之后,分析器才可以通过 GlobalOptions 获取属性。获取时,需要分析器项目使用 TryGetValue 方法,且要求在属性前面加上 build_property. 前缀。下文的例子将会告诉大家具体的获取方法

这里还存在一个问题,那就是属性的时机,如果属性的赋值是在分析器执行完成之后再赋值,那自然会让分析器拿不到符合预期的属性内容。而如果属性过早赋值,可能属性本身的逻辑无法实现。因此需要找到一个最迟的时机,这是在分析器可以获取到属性内容的最后时机,如以下代码,可以放在 GenerateMSBuildEditorConfigFileCore 执行之前

  <Target Name="Xxxxxxxx" BeforeTargets="GenerateMSBuildEditorConfigFileCore">
<PropertyGroup>
<MyCustomProperty>xxxxx</MyCustomProperty>
</PropertyGroup>
</Target>

如果属性能够一开始就赋值,那推荐就是一开始就赋值。如果属性有其他依赖,那推荐使用类似上面代码的写法。如果属性需要在 GenerateMSBuildEditorConfigFileCore 才获取到内容的,那就凉凉了,需要修改实现

完成配置之后,开始编写分析器项目的代码,由于分析器项目采用的是增量源代码构建,逻辑上会比较复杂一些。在增量源代码生成里面,是没有直接提供 GlobalOptions 用来访问的,而是需要按照增量的方法,先过滤出感兴趣的内容。在感兴趣的内容发生变更或初始化时,将会触发实际执行的逻辑,在实际执行的逻辑,通过过滤条件的输出结果,拿到参数,生成代码

先开始搭建基础的代码,在 LainewihereJerejawwerye.Analyzers 新建一个叫 CodeCollectionIncrementalGenerator 的类型,此类将用来编写本文的核心代码。类名随意,可以自己修改

using System;
using Microsoft.CodeAnalysis; namespace LainewihereJerejawwerye.Analyzers
{
[Generator(LanguageNames.CSharp)]
public class CodeCollectionIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 在这里编写代码
}
}
}

使用增量代码生成,可以看到继承的是 IIncrementalGenerator 接口,需要实现的只有初始化函数,而不是一个初始化和一个执行函数。在增量代码生成里,需要在此初始化函数里面完成所有代码逻辑。但不代表着就是在初始化函数里面执行完成,因为实际上在此初始化函数里面,更多的是注入各个委托,在各个委托里面实现逻辑。在编写代码过程中,各个委托将会按需被调度执行,从而完成增量代码生成

按照增量代码生成的编写要求,第一步是声明对什么感兴趣,也就是一次过滤。只有满足条件的内容发生变更或初始化时,才会触发后续逻辑,同时过滤的结果也会作为后续逻辑的输入参数。本文这里需要的只是配置属性而已。配置属性都放在 AnalyzerConfigOptionsProvider 里,换句话说,我可以对整个 AnalyzerConfigOptionsProvider 都感兴趣。于是 AnalyzerConfigOptionsProvider 属性就是我的过滤条件

于是将 AnalyzerConfigOptionsProvider 作为参数条件传入到 RegisterImplementationSourceOutput 方法里面,也就是只有在配置初始化或变更时,才会触发传入 RegisterImplementationSourceOutput 的委托

            context.RegisterImplementationSourceOutput(context.AnalyzerConfigOptionsProvider,
(productionContext, provider) =>
{
// 这里的代码只有当配置初始化或变更时才会被执行
};

这里拿到的 provider 就是项目的配置了,其中本文期望的 csproj 项目文件的属性也就在 GlobalOptions 属性里面,可以通过如下代码进行获取

            context.RegisterImplementationSourceOutput(context.AnalyzerConfigOptionsProvider,
(productionContext, provider) =>
{
var text = string.Empty; // 通过 csproj 等 PropertyGroup 里面获取
// 需要将可见的,放入到 CompilerVisibleProperty 里面
// 需要加上 `build_property.` 前缀
if (provider.GlobalOptions.TryGetValue("build_property.MyCustomProperty", out var myCustomProperty))
{
text += " " + myCustomProperty;
}
};

如此即可拿到属性的内容,放入到 text 变量。接着再使用本文已开始的生成代码,完成之后的代码如下

        public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterImplementationSourceOutput(context.AnalyzerConfigOptionsProvider,
(productionContext, provider) =>
{
var text = string.Empty; // 通过 csproj 等 PropertyGroup 里面获取
// 需要将可见的,放入到 CompilerVisibleProperty 里面
// 需要加上 `build_property.` 前缀
if (provider.GlobalOptions.TryGetValue("build_property.MyCustomProperty", out var myCustomProperty))
{
text += " " + myCustomProperty;
} var code = @"using System;
namespace LainewihereJerejawwerye
{
public static class Foo
{
public static void F1()
{
Console.WriteLine(""" + text + @""");
}
}
}";
productionContext.AddSource("Demo", code);
});
}

尝试在 LainewihereJerejawwerye 项目调用一下

Foo.F1();

然后运行 LainewihereJerejawwerye 项目,可以看到输出了 MyCustomProperty 属性的内容,证明获取 csproj 项目文件里的属性成功

本文的代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin d4fee9a801c5d2591162ce4280ac06906029ef48

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin d4fee9a801c5d2591162ce4280ac06906029ef48

获取代码之后,进入 LainewihereJerejawwerye 文件夹

更多源代码生成,请看官方的 Source Generators Cookbook

更多关于我博客请参阅 博客导航

IIncrementalGenerator 增量 Source Generator 生成代码入门 读取 csproj 项目文件的属性配置的更多相关文章

  1. mybatis Generator生成代码及使用方式

    本文原创,转载请注明:http://www.cnblogs.com/fengzheng/p/5889312.html 为什么要有mybatis mybatis 是一个 Java 的 ORM 框架,OR ...

  2. mybatis之generator生成代码

    首先在pom文件中引入以下代码 <plugin> <groupId>org.mybatis.generator</groupId> <artifactId&g ...

  3. springboot快速入门(二)——项目属性配置(日志详解)

    一.概述 application.properties就是springboot的属性配置文件 在使用spring boot过程中,可以发现项目中只需要极少的配置就能完成相应的功能,这归功于spring ...

  4. 用org.mybatis.generator 生成代码

    1:引入pom 2:增加生成配置xml: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ...

  5. 2016.7.12 eclispe使用mybatis generator生成代码时提示project E is not exist

    运行mybatis-generator之后,出现错误:project E is not exist   错误原因:使用了项目的绝对路径. http://bbs.csdn.net/topics/3914 ...

  6. mybatis Generator生成代码及使用方式(转载)

    转载自:http://www.cnblogs.com/fengzheng/p/5889312.html 文章很棒,很不错,转了.

  7. 把Mybatis Generator生成的代码加上想要的注释

    作者:王建乐 1 前言 在日常开发工作中,我们经常用Mybatis Generator根据表结构生成对应的实体类和Mapper文件.但是Mybatis Generator默认生成的代码中,注释并不是我 ...

  8. IDEA使用mybatis generator自动生成代码

    主要就三步: 1.pom 文件中引入jar包并配置 build 属性 <dependencies> <!-- 自动生产mapper Begin! --> <depende ...

  9. springboot 使用mybatis-generator自动生成代码

    这里只介绍mybatis generator生成代码 一.pom配置 在build-->plugins-->添加plugin <plugin> <groupId>o ...

  10. Java 读取application.properties配置文件中配置

    实际开发中若需要读取配置文件application.properties中的配置,代码如下.例:读取配置文件中name属性配置值: 代码如下: import org.springframework.c ...

随机推荐

  1. Bitmap优化详谈

    目录介绍 01.如何计算Bitmap占用内存 1.1 如何计算占用内存 1.2 上面方法计算内存对吗 1.3 一个像素占用多大内存 02.Bitmap常见四种颜色格式 2.1 什么是bitmap 2. ...

  2. Sealos 云开发:Laf 出嫁了,与 Sealos 正式结合!

    千呼万唤始出来,Laf 云开发最近已正式与 Sealos 融合,入住 Sealos!大家可以登录 Sealos 公有云 体验和使用,现在正式介绍一下 Sealos 云开发. Sealos 云开发是什么 ...

  3. Linux基础操作一

    开启Linux操作系统,要求以root用户登录GNOME图形界面,语言支持选择为汉语 开启虚拟机→Username:root→Password:"(注册时所创建的密码,比如"123 ...

  4. 解决cv2. imread、imwrite无法读取或保存中文路径图片问题

    cv2.imwrite(filename, img) 修改为 cv2.imencode('.jpg', img)[1].tofile(filename) cv2.imread(filename, cv ...

  5. HashMap集合的map.values()返回的Collection集合执行add方法报空指针问题

    一.方法1. private Collection<String> setPermissionTenant(List<SysPermission> ls, int tenant ...

  6. PS-AXI-PL流水灯设计(2)

    PS-AXI-PL流水灯设计(2) 1.实验原理 承接上一次的实验,这里对AXI的总线结构做出分析,将AXI的理论具体对应到设计上去.为后面自己设计AXI的发送和接受器做好准备. 2.实验操作 (1) ...

  7. 【AI】『Suno』哎呦不错呦,AI界的周董,快来创作你的歌曲吧!

    前言 缘由 Suno AI的旋风终于还是吹到了音乐圈 事情起因: 朋友说他练习时长两天半,用Suno发布了首张AI音乐专辑.震惊之余,第一反应是音乐圈门槛也这么低了,什么妖魔鬼怪都可以进军了嘛! 好奇 ...

  8. 如何自动申请免费的HTTPS证书?

    在购买域名的时候我相信很多人都遇到了对于证书的问题,之前我也是使用阿里云的免费一年的证书,那时候感觉还好,一年更换一次,但是近期阿里云对于证书的过期时间直接砍到了三个月!让我难以接受,所以我在想吧他直 ...

  9. #状压dp,背包,贪心#洛谷 5997 [PA2014]Pakowanie

    题目 你有 \(n\) 个物品和 \(m\) 个包.物品有重量,且不可被分割: 包也有各自的容量.要把所有物品装入包中,至少需要几个包? 分析 考虑物品的数量很小,首先优先选容量大的背包, 设\(f[ ...

  10. 可视化库 pygal 无法保存成本地文件

    问题:在使用可视化库 pygal 保存图像到本地时,出现报错 第一次报错是,提示没有  cairosvg  这个模块,所以直接通过 pip 安装 pip install cairosvg 安装完了以后 ...