本文告诉大家如何在使用 IIncrementalGenerator 进行增量的 Source Generator 生成代码时,如何判断两个程序集之间是否存在 InternalsVisibleTo 关系

当获取到两个程序集时,如果要开始准备生成相关代码,可能会因为不知道两个程序集之间是否存在 InternalsVisibleTo 关系,也就是是否应该导出其 internal 的类型而困扰。在能够获取到 IAssemblySymbol 类型的对象,即可通过 GivesAccessTo 方法判断两个程序集的 InternalsVisibleTo 关系

这个 GivesAccessTo 方法可以获取到当前的程序集对给定的程序集参数是否为 internal 可见

以下是详细的例子代码

本文的例子的任务是编写一个 Roslyn 分析器,在分析器里面使用 IIncrementalGenerator 增量 Source Generator 生成代码,获取到对当前正在分析的项目设置 InternalsVisibleTo 的引用程序集,将程序集名作为生成代码的部分,让正在被分析的项目可以编写代码输出有哪些程序集是 internal 可见的

先新建以下 .NET 7 控制台项目,分别是名为 Analyzers 和 App 和 Lib1 和 Lib2 项目

在 Lib1 和 Lib2 里面存放一些 internal 的类型,这两个项目将被当成类库项目被 App 项目所引用。在 Lib2 里面添加一个 AssemblyInfo.cs 文件,在 AssemblyInfo.cs 文件里面记录 InternalsVisibleTo 给到 App 程序集,如以下代码。于是 Lib1 没有对 App 项目 internal 可见,而 Lib2 对 App 项目 internal 可见

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("App")]

设置 App 项目引用 Lib1 和 Lib2 项目,且引用 Analyzers 项目作为分析器项目。完成设置的 App 项目的 csproj 项目文件代码大概如下

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

  <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup> <ItemGroup>
<ProjectReference Include="..\Lib1\Lib1.csproj" />
<ProjectReference Include="..\Lib2\Lib2.csproj" />
<ProjectReference Include="..\Analyzers\Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup> </Project>

大概的项目组织如下图

修改 Analyzers 项目,让这个项目成为 Roslyn 分析器项目,修改之后的 csproj 项目文件代码如下

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

  <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<LangVersion>latest</LangVersion> <Nullable>enable</Nullable>
</PropertyGroup> <ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
</ItemGroup> </Project>

这里的 TargetFramework 设置为 netstandard2.0 是为了同时让 VisualStudio 和 dotnet 开森

详细关于以上 csproj 项目文件代码里的 EnforceExtendedAnalyzerRules 的属性,请参阅 Roslyn 分析器 EnforceExtendedAnalyzerRules 属性的作用

以上的 LangVersion 属性设置为 latest 表示使用最新的语言版本,详细请参阅 VisualStudio 使用三个方法启动最新 C# 功能

通过以上配置即可完成项目的初始化逻辑。回到咱这个例子的任务上,就是在 Analyzers 分析器项目编写代码,分析 App 项目所引用的程序集里面的存在哪些程序集对 App 程序集设置了 internal 可见

完成准备工作之后,接下来开始本文的核心逻辑编写。先在 Analyzers 分析器项目上新建一个继承 IIncrementalGenerator 接口的 FooTelescopeIncrementalGenerator 类型,接下来的核心逻辑将在 FooTelescopeIncrementalGenerator 的 Initialize 开始编写

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; using Microsoft.CodeAnalysis; namespace Analyzers; [Generator(LanguageNames.CSharp)]
public class FooTelescopeIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
... // 忽略代码
}
}

根据上文的描述,咱需要先从 context 里面的 CompilationProvider 获取到引用的程序集,代码如下

[Generator(LanguageNames.CSharp)]
public class FooTelescopeIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var internalsVisibleFromAssemblyNameListIncrementalValueProvider = context.CompilationProvider.Select((compilation, token) =>
{
... // 忽略代码
});
}
}

通过 compilation 的 SourceModule 属性的 ReferencedAssemblySymbols 即可获取到所有的引用程序集,如以下代码

[Generator(LanguageNames.CSharp)]
public class FooTelescopeIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var internalsVisibleFromAssemblyNameListIncrementalValueProvider = context.CompilationProvider.Select((compilation, token) =>
{
// 获取到所有引用程序集
var referencedAssemblySymbols = compilation.SourceModule.ReferencedAssemblySymbols; ... // 忽略代码
});
}
}

从 compilation 里面拿到的 Assembly 属性就是当前正在分析的程序集,在本文这里就是 App 程序集。而 referencedAssemblySymbols 里面都是当前的 App 程序集所引用的程序集。判断引用的程序集是否对当前正在分析的程序集设置了 internal 可见,即可通过 GivesAccessTo 方法进行判断,代码如下

[Generator(LanguageNames.CSharp)]
public class FooTelescopeIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var internalsVisibleFromAssemblyNameListIncrementalValueProvider = context.CompilationProvider.Select((compilation, token) =>
{
var internalsVisibleFromAssemblyNameList = new List<string>(); // 获取到所有引用程序集
var referencedAssemblySymbols = compilation.SourceModule.ReferencedAssemblySymbols; foreach (var referencedAssemblySymbol in referencedAssemblySymbols)
{
var name = referencedAssemblySymbol.Name; if (referencedAssemblySymbol.GivesAccessTo(compilation.Assembly))
{
internalsVisibleFromAssemblyNameList.Add(name);
}
} return internalsVisibleFromAssemblyNameList;
}); ... // 忽略代码
}
}

接下来将收集到的给当前正在分析的程序集设置了 internal 可见的程序集列表输出到生成代码里面,如以下代码

{% raw %}

using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis; namespace Analyzers; [Generator(LanguageNames.CSharp)]
public class FooTelescopeIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var internalsVisibleFromAssemblyNameListIncrementalValueProvider = context.CompilationProvider.Select((compilation, token) =>
{
var internalsVisibleFromAssemblyNameList = new List<string>(); // 获取到所有引用程序集
var referencedAssemblySymbols = compilation.SourceModule.ReferencedAssemblySymbols; foreach (IAssemblySymbol? referencedAssemblySymbol in referencedAssemblySymbols)
{
var name = referencedAssemblySymbol.Name; if (referencedAssemblySymbol.GivesAccessTo(compilation.Assembly))
{
internalsVisibleFromAssemblyNameList.Add(name);
}
} return internalsVisibleFromAssemblyNameList;
}); context.RegisterSourceOutput(internalsVisibleFromAssemblyNameListIncrementalValueProvider, (productionContext, list) =>
{
var code = $@"
public static class InternalsVisibleToHelper
{{
public static IEnumerable<string> GetAllInternalsVisibleFromAssemblyName()
{{
{(string.Join("\r\n", list.Select(t => $@"yield return ""{t}"";")))}
}}
}}";
productionContext.AddSource("InternalsVisibleToHelper", code);
});
}
}

{% endraw %}

回到 App 项目里面,编辑 Program.cs 文件,输出以上生成的 InternalsVisibleToHelper 类型里面的 GetAllInternalsVisibleFromAssemblyName 方法返回内容到控制台,如以下代码

foreach (var name in InternalsVisibleToHelper.GetAllInternalsVisibleFromAssemblyName())
{
Console.WriteLine(name);
}

运行 App 项目,可以看到控制台很符合预期的只输出了 Lib2 程序集

通过以上的代码,即可在 Roslyn 分析器里面,了解程序集之间的 internal 关系,从而可以生成出更加符合预期的代码

本文所有代码放在 githubgitee 上,可以通过以下方式获取整个项目的代码

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

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

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

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

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

IIncrementalGenerator 判断程序集之间可见关系的更多相关文章

  1. 判断线段之间的关系(D - Intersecting Lines POJ - 1269 )

    题目链接:https://vjudge.net/contest/276358#problem/D 题目大意:每一次给你两条直线,然后问你这两条直线的关系(平行,共线,相交(输出交点)). 具体思路:先 ...

  2. 实验12:Problem D: 判断两个圆之间的关系

    Home Web Board ProblemSet Standing Status Statistics   Problem D: 判断两个圆之间的关系 Problem D: 判断两个圆之间的关系 T ...

  3. [转] valuestack,stackContext,ActionContext.之间的关系

    三者之间的关系如下图所示: ActionContext  一次Action调用都会创建一个ActionContext  调用:ActionContext context = ActionContext ...

  4. 谈谈Activiti中流程对象之间的关系

    详细见:http://www.kafeitu.me/activiti/2012/03/22/workflow-activiti-action.html (咖啡兔好牛!) 详细见: http://blo ...

  5. PHP中空字符串介绍0、null、empty和false之间的关系

    PHP中空字符串介绍0.null.empty和false之间的关系 作者: 字体:[增加 减小] 类型:转载 时间:2012-09-25   用PHP开发那么久,PHP中空字符串.0.null.emp ...

  6. JS对象之间的关系

    JS对象类型 JS中,可以将对象分为"内部对象"."宿主对象"和"自定义对象"三种. 1.本地对象 ECMA-262定义为"独立于 ...

  7. valuestack,stackContext,ActionContext.之间的关系

    者之间的关系如下图所示: relation ActionContext 一次Action调用都会创建一个ActionContext 调用:ActionContext context = ActionC ...

  8. [转]C#综合揭秘——细说进程、应用程序域与上下文之间的关系

    引言 本文主要是介绍进程(Process).应用程序域(AppDomain)..NET上下文(Context)的概念与操作.虽然在一般的开发当中这三者并不常用,但熟悉三者的关系,深入了解其作用,对提高 ...

  9. 正确理解 AsyncTask,Looper,Handler三者之间的关系(基于android 4.0)

    Looper 和Handler 是理解好AsyncTask的一个基础,我们可以先从这里开始,先给出一个主线程和子线程互相通信的例子. package com.example.loopertest; i ...

  10. 泛型编程、STL的概念、STL模板思想及其六大组件的关系,以及泛型编程(GP)、STL、面向对象编程(OOP)、C++之间的关系

    2013-08-11 10:46:39 介绍STL模板的书,有两本比较经典: 一本是<Generic Programming and the STL>,中文翻译为<泛型编程与STL& ...

随机推荐

  1. Python实现简易版Netcat

    Netcat Netcat是一种网络工具,也称为"nc",可用于在计算机网络之间进行TCP/IP或UDP连接.它可以用于连接到其他计算机上的端口,发送和接收数据,扫描端口以及创建服 ...

  2. 记录--H5页面对接微信支付踩坑杂记

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 背景 应用背景:vite搭建的vue3项目 需求背景:功能都涉及了支付业务,故需要和外部支付系统对接 外部支付系统:聚合支付.微信小程序支 ...

  3. 记录--uni-app中安卓包检查更新、新版本下载、下载进度条显示功能实现

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 需求描述 如果想要做一个app的话,可以有很多种选择方案,uni-app是其中的一个性价比高一些(坑多一些)的方案.本文记录一下,uni- ...

  4. vue中elementui表格错位问题解决(一次到位)

    引入element-resize-detector 在项目引入插件(用来监听元素宽度变化) npm install element-resize-detector --save 在文件中引入(写在sc ...

  5. mybatis xml 文件 sql include 的用法

    mybatis xml 文件中对于重复出现的sql 片段可以使用标签提取出来,在使用的地方使用标签引用即可具体用法如下: <sql id="Base_Column_List" ...

  6. DynamicHead:基于像素级路由机制的动态FPN | NIPS 2020

    论文提出了细粒度动态detection head,能够基于路由机制动态地融合不同FPN层的像素级局部特征进行更好的特征表达.从设计的路由空间来看是一个十分耗时的操作,但是作者设计的高效路由器实际计算十 ...

  7. kingbaseES V8R6 备份恢复案例 -- sys_rman备份“DSO support..."故障

    案例说明: 在通过sys_rman执行备份时,出现"DSO support...."错误,如下图所示: sys_log日志: 适用版本: KingbaseES V8R6 一.问题分 ...

  8. jenkens2权威指南

    第1章 Jenkins简介 Jenkins 2是什么 JobConfigHistory:这个插件可以追溯XML配置的历史版本信息, 并且允许你查看每次变更的内容. JenkinsFile Jenkin ...

  9. spark和hadoop的区别

    hadoopHadoop是一个由Apache基金会所开发的分布式系统基础架构. 用户可以在不了解分布式底层细节的情况下,开发分布式程序.充分利用集群的威力进行高速运算和存储. Hadoop实现了一个分 ...

  10. C++ atomic

    atomic 每个 std::atomic 模板的实例化和全特化定义一个原子类型.若一个线程写入原子对象,同时另一线程从它读取,则行为良好定义. 另外,对原子对象的访问可以建立线程间同步,并按 std ...