0. 前言

上接:用 Roslyn 做个 JIT 的 AOP

作为第二篇,我们基于Source Generators做个AOP静态编织小实验。

内容安排如下:

  • source generators 是什么?
  • 做个达到上篇Jit 一样的效果的demo
  • source generators还存在什么问题?

1. Source Generators 是什么?

1.1 核心目的

开启dotnet平台的编译时元编程功能,

让我们能在编译时期动态创建代码,

同时考虑IDE的集成,让体验更舒适。

官方文档:https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md

展开我们思想的翅膀

我们能以此做各种事情:

  • 生成实体json 等序列化器代码
  • AOP
  • 接口定义生成httpclient调用代码
  • 等等

如下是官方认为会受益的部分功能列表:

  • ASP.Net: Improve startup time
  • Blazor and Razor: Massively reduce tooling burden
  • Azure Functions: regex compilation during startup
  • Azure SDK
  • gRPC
  • Resx file generation
  • System.CommandLine
  • Serializers
  • SWIG

1.2 目前其设计和使用准则

允许开发者能在编译时动态创建添加新代码到我们程序里面

只能新增代码,不能修改已有代码

当无法生成源时,生成器应当产生诊断信息,通知用户问题所在。

可能访问其他文件非c#源代码文件。

无序运行模式,每个生成器都只能拥有相同的输入编译,即不能用其他生成器的生成结果进行再次生成。

生成器的运行类似于分析器。

2. 实验:代理模式的静态编织

2.1 创建一个Source Generators项目

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<PropertyGroup>
<RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json ;$(RestoreAdditionalProjectSources)</RestoreAdditionalProjectSources>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" PrivateAssets="all"/>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0" PrivateAssets="all" />
</ItemGroup> </Project>

2.2 创建SourceGenerator

需要继承 Microsoft.CodeAnalysis.ISourceGenerator

namespace Microsoft.CodeAnalysis
{
public interface ISourceGenerator
{
void Initialize(InitializationContext context);
void Execute(SourceGeneratorContext context);
}
}

并通过[Generator]标识启用

所以我们就可以做一个这样的代理生成器:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AopAnalyzer
{
[Generator]
public class ProxyGenerator : ISourceGenerator
{
public void Execute(SourceGeneratorContext context)
{
// retreive the populated receiver
if (!(context.SyntaxReceiver is SyntaxReceiver receiver))
return;
try
{
// 简单测试aop 生成
Action<StringBuilder, IMethodSymbol> beforeCall = (sb, method) => { };
Action<StringBuilder, IMethodSymbol> afterCall = (sb, method) => { sb.Append("r++;"); };
// 获取生成结果
var code = receiver.SyntaxNodes
.Select(i => context.Compilation.GetSemanticModel(i.SyntaxTree).GetDeclaredSymbol(i) as INamedTypeSymbol)
.Where(i => i != null && !i.IsStatic)
.Select(i => ProxyCodeGenerator.GenerateProxyCode(i, beforeCall, afterCall))
.First();
context.AddSource("code.cs", SourceText.From(code, Encoding.UTF8));
}
catch (Exception ex)
{
// 失败汇报
context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("n001", ex.ToString(), ex.ToString(), "AOP.Generate", DiagnosticSeverity.Warning, true), Location.Create("code.cs", TextSpan.FromBounds(0, 0), new LinePositionSpan())));
}
}
public void Initialize(InitializationContext context)
{
// Register a syntax receiver that will be created for each generation pass
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
}
/// <summary>
/// 语法树定义收集器,可以在这里过滤生成器所需
/// </summary>
internal class SyntaxReceiver : ISyntaxReceiver
{
internal List<SyntaxNode> SyntaxNodes { get; } = new List<SyntaxNode>();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is TypeDeclarationSyntax)
{
SyntaxNodes.Add(syntaxNode);
}
}
}
}
}

具体的代理代码生成逻辑:

using Microsoft.CodeAnalysis;
using System;
using System.Linq;
using System.Text;
namespace AopAnalyzer
{
public static class ProxyCodeGenerator
{
public static string GenerateProxyCode(INamedTypeSymbol type, Action<StringBuilder, IMethodSymbol> beforeCall, Action<StringBuilder, IMethodSymbol> afterCall)
{
var sb = new StringBuilder();
sb.Append($"namespace {type.ContainingNamespace.ToDisplayString()} {{");
sb.Append($"{type.DeclaredAccessibility.ToString().ToLower()} class {type.Name}Proxy : {type.ToDisplayString()} {{ ");
foreach (var method in type.GetMembers().Select(i => i as IMethodSymbol).Where(i => i != null && i.MethodKind != MethodKind.Constructor))
{
GenerateProxyMethod(beforeCall, afterCall, sb, method);
}
sb.Append(" } }");
return sb.ToString();
}
private static void GenerateProxyMethod(Action<StringBuilder, IMethodSymbol> beforeCall, Action<StringBuilder, IMethodSymbol> afterCall, StringBuilder sb, IMethodSymbol method)
{
var ps = method.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}");
sb.Append($"{method.DeclaredAccessibility.ToString().ToLower()} override {method.ReturnType.ToDisplayString()} {method.Name}({string.Join(",", ps)}) {{");
sb.Append($"{method.ReturnType.ToDisplayString()} r = default;");
beforeCall(sb, method);
sb.Append($"r = base.{method.Name}({string.Join(",", method.Parameters.Select(p => p.Name))});");
afterCall(sb, method);
sb.Append("return r; }");
}
}
}

可以看到和之前jit的代码非常相似

2.3 测试一下

2.3.1 新建测试项目

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>preview</LangVersion> //新版本才有哦,现在还未正式发布
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AopAnalyzer\AopAnalyzer.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" /> //设置为分析器项目
</ItemGroup>
</Project>

2.3.2 测试代码

using System;
namespace StaticWeaving_SourceGenerators
{
static class Program
{
static void Main(string[] args)
{
var proxy = new RealClassProxy(); // 对,生成的新代码可以ide里面直接用,就是这么强大,只要编译一次就看的到了
var i = 5;
var j = 10;
Console.WriteLine($"{i} + {j} = {(i + j)}, but proxy is {proxy.Add(i, j)}");
Console.ReadKey();
}
} public class RealClass
{
public virtual int Add(int i, int j)
{
return i + j;
}
}
}

输出结果:

5 + 10 = 15, but proxy is 16

cpu 和内存,自然完美:

完整的demo 放在 https://github.com/fs7744/AopDemoList

3. Source Generators 还有什么严重的缺陷呢?

3.1 不能引入其他程序集,比如nuget包

比如我们引入Newtonsoft.Json,就会造成如下编译异常:

System.IO.FileNotFoundException: 未能加载文件或程序集“Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed”或它的某一个依赖项。系统找不到指定的文件。

这就造成我们很难利用现有的包做各种事情,以及怎么把我们的代码生成器提供给别人使用了

有同学就这一点提了issue : https://github.com/dotnet/roslyn/issues/45060

感兴趣的同学可以去赞一赞

3.2 不能debug(其实我接受这点)

可以通过UT测试 debug的

3.3 生成结果不能查看

对使用生成器的人会比较麻烦,他不知道具体成什么样子了,特别是生成有错误的时候。

基于 Source Generators 做个 AOP 静态编织小实验的更多相关文章

  1. 基于Source Insight_Scan的C/C++静态代码检查工具安装说明

    基于Source Insight_Scan的C/C++静态代码检查工具安装说明   本文链接:https://blog.csdn.net/M19930517/article/details/79977 ...

  2. 使用基于Roslyn的编译时AOP框架来解决.NET项目的代码复用问题

    理想的代码优化方式 团队日常协作中,自然而然的会出现很多重复代码,根据这些代码的种类,之前可能会以以下方式处理 方式 描述 应用时可能产生的问题 硬编码 多数新手,或逐渐腐坏的项目会这么干,会直接复制 ...

  3. 基于 Annotation 拦截的 Spring AOP 权限验证方法

    基于 Annotation 拦截的 Spring AOP 权限验证方法 转自:http://www.ibm.com/developerworks/cn/java/j-lo-springaopfilte ...

  4. AOP静态代理解析1-标签解析

    AOP静态代理使用示例见Spring的LoadTimeWeaver(代码织入) Instrumentation使用示例见java.lang.instrument使用 AOP的静态代理主要是在虚拟机启动 ...

  5. 基于Quqrtz.NET 做的任务调度管理工具

    基于Quqrtz.NET 做的任务调度管理工具 国庆前,需求让我看了一下任务调度的数据表设计.和之前一样,有100多个字段,p1 ~ p100, 我说这是干嘛啊!按这写,写死去了! 然后在网上搜了一下 ...

  6. Spring Aop(二)——基于Aspectj注解的Spring Aop简单实现

    转发地址:https://www.iteye.com/blog/elim-2394762 2 基于Aspectj注解的Spring Aop简单实现 Spring Aop是基于Aop框架Aspectj实 ...

  7. 【开源】.Net Aop(静态织入)框架 BSF.Aop

    BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费): 实现前后Aop切面和INotifyPropertyChanged注入方式. 开源地址: ...

  8. 基于trie树做一个ac自动机

    基于trie树做一个ac自动机 #!/usr/bin/python # -*- coding: utf-8 -*- class Node: def __init__(self): self.value ...

  9. 给Source Insight做个外挂系列之六--“TabSiPlus”的其它问题

    关于如何做一个Source Insight外挂插件的全过程都已经写完了,这么一点东西拖了一年的时间才写完,足以说明我是一个很懒的人,如果不是很多朋友的关心和督促,恐怕是难以完成了.许多朋友希望顺着本文 ...

随机推荐

  1. 大数据和Hadoop平台介绍

    大数据和Hadoop平台介绍 定义 大数据是指其大小和复杂性无法通过现有常用的工具软件,以合理的成本,在可接受的时限内对其进行捕获.管理和处理的数据集.这些困难包括数据的收入.存储.搜索.共享.分析和 ...

  2. NLP之统计句法分析(PCFG+CYK算法)

    一.认识句法分析 首先,了解一下句法分析到底是什么意思?是做什么事情呢?顾名思义,感觉是学习英语时候讲的各种句法语法.没错!这里就是把句法分析过程交给计算机处理,让它分析一个句子的句法组成,然后更好理 ...

  3. Java学习之Swing Gui编程

    Java学习之Swing Gui编程 0x00 前言 前面的使用的Gui是基于Awt 去进行实现,但是在现实写Gui中 AWT实际运用会比较少. 0x01 Swing 概述 AWT 和Swing 区别 ...

  4. 数据恢复当选EasyRecovery,设备不再受限

    我们在逐渐适应信息电子化的同时,也有一些潜在的麻烦接踵而来,其中较为常见的就是文件和数据的保存问题. 显然,设备的存储空间是有限的,这就不可避免地会出现数据被删除.覆盖或丢失的现象,如果丢失的是重要数 ...

  5. Java IDEA 根据mybatis-generator-core自动生成代码支持sqlserver获取备注(二)

    mybatis generator代码生成虽然好用,但是好像不支持sqlserver获取备注信息,这里我主要分享mybatis generator改写后支持sqlserver获取备注信息,mysql以 ...

  6. jenkins运行错误解决办法

    本地搭建好jenkis服务器,运行然后登陆上管理后台 构建好运行命令,然后进行构建出现如下错误说没有python运行环境 ,本地已经配置好python环境,并且终端能正常运行. 解决方法:jenkin ...

  7. 2019年第十届蓝桥杯【C++省赛B组】D、E、G、H、I题解

    这场有几道题目思路,在之前比赛中遇到过 D. 数的分解 #枚举 题意 将\(2019\)分解成\(3\)个各不相同的正整数之和,并且每个正整数都不包含数字\(2\)和\(4\),一共有多少种分解方法? ...

  8. 什么,kafka能够从follower副本读数据了 —kafka新功能介绍

    最近看了kafka2.4新版本的一些功能特性,不得不说,在kafka2.0以后,kafka自身就比较少推出一些新的feature了,基本都是一些修修补补的东西.倒是kafka connect和kafk ...

  9. 我与UML相爱相杀的狗血日常

    前言 该怎么说呢,在专业母亲的包办婚姻和我不得不为日后的百万家产[日后的百万年薪,我怕是在做梦]下,我和UML的婚后生活正式开始了.第一天回娘家我亓老师就给出了她最爱的编程作业.说实话,我当初以头发为 ...

  10. python删除list中的空list

    list1 = [[], [], [], [], [], 'text', 'text2', [], 'moreText'] 如何删除空列表,以便我得到: list2 = ['text', 'text2 ...