[C#]SourceGenerator实战: 对任意对象使用await吧!!!
[C#]SourceGenerator实战: 对任意对象使用await吧!!!
前言
本文记录一次简单的 SourceGenerator 实战,最终实现可以在代码中 await 任意类型对象,仅供娱乐,请勿在生产环境中使用!!!
关键技术:
- SourceGenerator - 在编译时动态生成代码的技术。官方文档:源生成器
- 关于 IncrementalGenerator的基本使用可以参考 dotnet 用 SourceGenerator 源代码生成技术实现中文编程语言
- SourceGenerator除了提供附加文件进行代码生成,还有丰富的语法树、类型等分析API
- SourceGenerator只能- 拓展代码,不能- 替换代码
 
- Await anything - C#中的 async/await最终由编译器编译为状态机,其核心逻辑在于await对象需要实现符合要求的GetAwaiter方法,这个方法可以是拓展方法
- 参见官方博客 await anything;
 
- C#中的 
那么要实现对任何对象的 await 我们的思路大概如下:
- 找到所有的 await语法
- 检查 await的对象是否有GetAwaiter方法
- 为没有 GetAwaiter方法的对象生成GetAwaiter拓展方法
得益于 SourceGenerator 丰富的分析API,我们可以很容易的办到这件事
实现源生成器
GetAwaiter拓展方法模板
我们先来实现一个可以让 TargetType 支持 await 的拓展方法类模板:
using System.Runtime.CompilerServices;
namespace System.Threading.Tasks
{
    public static class GetAwaiterExtension_TargetTypeName
    {
        public static TaskAwaiterFor_TargetTypeName GetAwaiter(this TargetType value)
        {
            return new TaskAwaiterFor_TargetTypeName(value);
        }
        public readonly struct TaskAwaiterFor_TargetTypeName : ICriticalNotifyCompletion, INotifyCompletion
        {
            private readonly TargetType _value;
            public bool IsCompleted { get; } = true;
            public TaskAwaiterFor_TargetTypeName(TargetType value)
            {
                _value = value;
            }
            public TargetType GetResult()
            {
                return _value;
            }
            public void OnCompleted(Action continuation)
            {
                continuation();
            }
            public void UnsafeOnCompleted(Action continuation)
            {
                continuation();
            }
        }
    }
}
- 将类型放在命名空间 System.Threading.Tasks下,可以在使用的时候不需要额外的命名空间引用;
- 由于我们已经有了需要返回的结果值,所以 Awaiter的IsCompleted始终为true,GetResult直接返回结果即可;
分析所有 await 语法,并筛选出需要为其生成 GetAwaiter 方法的类型
- 先建立一个 IncrementalGenerator[Generator(LanguageNames.CSharp)]
 public class GetAwaiterIncrementalGenerator : IIncrementalGenerator
 {
 public void Initialize(IncrementalGeneratorInitializationContext context)
 {
 }
 }
 
- 在 Initialize方法中筛选目标类型/// 使用语法提供器筛选出所有的 `await` 语法,并获取其类型
 var symbolProvider = context.SyntaxProvider.CreateSyntaxProvider((node, _) => node is AwaitExpressionSyntax //直接判断节点是否为 `AwaitExpressionSyntax` 即可筛选出所有 await 表达式
 , TransformAwaitExpressionSyntax) //从 await 表达式中解析出其尚不支持 await 的对象类型符号
 .Where(m => m is not null) //筛选掉无效的项
 .WithComparer(SymbolEqualityComparer.Default); //使用默认的符号比较器进行比较
 
- 直接使用表达式语法不太方便处理,我们实现表达式语法到类型符号的转换方法 TransformAwaitExpressionSyntaxprivate static ITypeSymbol? TransformAwaitExpressionSyntax(GeneratorSyntaxContext generatorSyntaxContext, CancellationToken cancellationToken)
 {
 //经过筛选,到达此处的节点一定是 AwaitExpressionSyntax
 var awaitExpressionSyntax = (AwaitExpressionSyntax)generatorSyntaxContext.Node; //如果 await 表达式语法的 await 对象仍然是 AwaitExpressionSyntax ,那么跳过此条记录
 //类似 "await await await 1;" 我们直接忽略前两个 await 表达式
 if (awaitExpressionSyntax.Expression is AwaitExpressionSyntax)
 {
 return null;
 } //使用 `SemanticModel` 可以分析出更具体的符号信息,比如类型,方法等
 //直接使用其提供的 `GetAwaitExpressionInfo` 可以从表达式语法获取 await 的详细信息
 var awaitExpressionInfo = generatorSyntaxContext.SemanticModel.GetAwaitExpressionInfo(awaitExpressionSyntax); //判断分析结果中此表达式是否包含 `GetAwaiter` 方法,如果不包含,那么我们需要为其生成
 if (awaitExpressionInfo.GetAwaiterMethod is null)
 {
 //`SemanticModel` 的 GetTypeInfo 方法可以获取一个表达式的类型符号信息
 //返回 await 对象的类型符号
 return generatorSyntaxContext.SemanticModel.GetTypeInfo(awaitExpressionSyntax.Expression).Type;
 } return null;
 }
 
为所有目标类型生成 GetAwaiter 拓展方法
由于只需要为相同类型生成一次 GetAwaiter 方法,所以我们需要将类型符号去重之后进行生成
- 直接将上面的 symbolProvider传递给RegisterSourceOutput方法的话,每次只会处理一个类型符号,我们无法去重
- 调用 symbolProvider的Collect方法,可以将前面步骤筛选出的所有类型符号作为一个集合进行处理
所以注册源码生成器可以这样写:
context.RegisterSourceOutput(symbolProvider.Collect(),  //将筛选的结果作为整体传递
                            (ctx, input) =>
                            {
                                //遍历去重后的类型符号
                                foreach (var item in input.Distinct(SymbolEqualityComparer.Default))
                                {
                                    //为每个去重后的类型生成 `GetAwaiter` 拓展方法
                                }
                            });
接下来使用之前写的拓展方法模板生成每个类型的 GetAwaiter 拓展方法即可:
//获取类型符号的完整访问类型名
var fullyClassName = item!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
//获取不包含无效符号的类名
var className = NormalizeClassName(fullyClassName);
//替换模板中的类型占位符为当前处理的目标类型
var code = templateCode.Replace("TargetTypeName", className)
                       .Replace("TargetType", fullyClassName);
//如果目标类型不是公开类型,那么拓展方法也应该不公开
if (item.DeclaredAccessibility != Accessibility.Public)
{
    code = code.Replace("public static class", "internal static class");
}
//将生成的代码添加到编译中
ctx.AddSource($"GetAwaiterFor_{className}.g.cs", code);
//将类型名称中不能作为类名的符号替换为_
private static string NormalizeClassName(string value)
{
    return value.Replace('.', '_')
                .Replace('<', '_')
                .Replace('>', '_')
                .Replace(' ', '_')
                .Replace(',', '_')
                .Replace(':', '_');
}
到这里我们就实现了所有的功能点,新建项目并引用分析器就可以 await 任何对象了,效果大概如下:

- 代码 - AwaitAnyObject.zip
- 也可以直接安装 NuGet 包 AwaitAnyObject进行游玩
[C#]SourceGenerator实战: 对任意对象使用await吧!!!的更多相关文章
- Progress.js – 为页面上的任意对象创建进度条效果
		Progress.js 是一个 JavaScript 和 CSS3 的库,它帮助开发人员为网页上的每个对象创建和管理进度条效果.你可以设计自己的模板,进度条或者干脆定制. 您可以使用 Progress ... 
- Js中找任意对象的原型方法及改造原型
		Java中有运行时类型识别,js可以很方便的模仿这个特性,因为所有js对象都有一个属性constructor(构造器),表示这个对象的构造方法,原型与构造方法同名,所以可以通过这儿知道任意对象的原型名 ... 
- (一一五)利用NSKeyedArchiver实现任意对象转为二进制
		[应用背景] 在数据库中存储数据时,如果对象过于复杂,又不必要创建复杂的表,可以直接把整个对象转化为二进制存入数据库字段,然后取出后再还原即可. [实现方法] 在PHP中,使用序列化和反序列化可以实现 ... 
- Java多线程6:Synchronized锁代码块(this和任意对象)
		一.Synchronized(this)锁代码块 用关键字synchronized修饰方法在有些情况下是有弊端的,若是执行该方法所需的时间比较长,线程1执行该方法的时候,线程2就必须等待.这种情况下就 ... 
- synchronized将任意对象作为对象监视器
		多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)同步代码块时,调用的效果就是按顺序执行,也就是同步的,阻塞的.这说明synchronized同 ... 
- IOS-将任意对象存进数据库
		model // // HMShop.h // 将任意对象存进数据库 // // Created by apple on 14/11/20. // Copyright (c) 2014年 heima. ... 
- 直接获取任意对象的 $('.xx').css('x') 值都是0
		<!-- 任意对象,直接获取他们的 x , y 都是为0: $('#xxx').css('x','y'); --> <!DOCTYPE html> <html lang= ... 
- C# 将任意对象快速转换为Json
		由于最近项目里面Model类特别多,而我需要编写所有数据交互的接口,传输的格式是json,以前都是通过循环List<T>中的对象向StringBuilder对象中Apped字符串生成jso ... 
- 【WePY小程序框架实战四】-使用async&await异步请求数据
		[WePY小程序框架实战一]-创建项目 [WePY小程序框架实战二]-页面结构 [WePY小程序框架实战三]-组件传值 async await 是对promise的近一步优化,既解决了promise链 ... 
随机推荐
- springboot的@ConditionalOnClass注解
			大家好,我是"良工说技术". 今天给大家带来的是springboot中的@ConditionalOnClass注解的用法.上次的@ConditionalOnBean注解还记得吗? ... 
- vue-cli 启动项目时空白页面
			vue-cli 启动项目时空白页面 在启动项目时 npm run serve / npm run dev 启动 vue 项目空白页:且终端及控制台都未报错 通过各种查阅发现在项目根目录中 vue-co ... 
- reactjs中使用threejs从0到1
			搭建本地开发环境 安装nodejs 按照 Create React App 安装指南创建一个新的项目 npx create-react-app react-three-demo 删除掉新项目中 src ... 
- Docker 06 部署Nginx
			参考源 https://www.bilibili.com/video/BV1og4y1q7M4?spm_id_from=333.999.0.0 https://www.bilibili.com/vid ... 
- Dart 异步编程(三):详细认识
			基本概念 普通任务按照顺序执行:异步任务将在未来的某个时间执行. 实际演示 void main() { // waitFuture 函数是一个异步函数,阻塞会发生在函数内部 waitFuture(); ... 
- [Blender] Blender 获取 Instance 的信息
			最近希望用 Blender 生成 Instance 的能力,将生成的导入游戏引擎中来渲染.Instance Rendering 是个好东西,特别是针对大场景,渲染成批的基本相同的物体的时候非常有用. ... 
- React报错之Parameter 'props' implicitly has an 'any' type
			正文从这开始~ 总览 当我们没有为函数组件或者类组件的props声明类型,或忘记为React安装类型声明文件时,会产生"Parameter 'props' implicitly has an ... 
- 前端利器躬行记(8)——VSCode插件研发
			VSCode提供了丰富的 API,可以借助编辑器扩展许多定制功能. 本次研发了一款名为 Search Method 的插件,在此记录整个研发过程. 一.准备工作 1)安装环境 首先是全局安装 yo 和 ... 
- 如何高效解决 C++内存问题,Apache Doris 实践之路|技术解析
			导读:Apache Doris 使用 C++ 语言实现了执行引擎,C++ 开发过程中,影响开发效率的一个重要因素是指针的使用,包括非法访问.泄露.强制类型转换等.本文将会通过对 Sanitizer 和 ... 
- 【必知必会】手把手教你配置MySQL环境变量——图文详解
			一.先决条件 假设我们已经成功安装MySQL数据库.如果还有小伙伴不知道如何安装MySQL数据库,可以在本文下留言,留言数超20,则出一期"手把手教你安装MySQL数据库--图文详解&quo ... 
