美文网首页Java成长之路
基于 Source Generators 做个 AOP 静态编织

基于 Source Generators 做个 AOP 静态编织

作者: 路人甲java | 来源:发表于2020-07-17 14:32 被阅读0次
    image.png

    0. 前言

    副标题:无价值人生记录.0:浪费 1000% 时间去做一个用来节省 1% 时间的“轮子玩具”(下:AOP实践2 Source Generators)

    上接:用 Roslyn 做个 JIT 的 AOP

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

    内容安排如下:

    • source generators 是什么?

    • 做个达到上篇Jit 一样的效果的demo

    • source generators还存在什么问题?

    1. Source Generators 是什么?

    1.1 核心目的

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

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

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

    展开我们思想的翅膀

    我们能以此做各种事情:

    • 生成实体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 和内存,自然完美:

    image

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

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

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

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

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

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

    可以通过UT测试 debug的

    3.3 生成结果不能查看

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

    4.后记

    毕竟该功能还是实验特性,距离完成还有一定距离,

    不过这样可以让语言的发展。

    相关文章

      网友评论

        本文标题:基于 Source Generators 做个 AOP 静态编织

        本文链接:https://www.haomeiwen.com/subject/ewyghktx.html