DotNet源生成器(Source Generators)Aop日志功能初试玩

由于各种各样的原因,不得不暂时放下在我的 Blazor 项目添加Aop日志记录功能。

但是又在偶然的情况下,得知了Source Generators即源码生成器,能在编译期间,自己构建 cs 源码进行编译。

这让我又燃起了添加Aop日志记录功能的希望!!SG 官方文档

具体的项目创建、项目引用、项目文件编辑等等细节问题在官方文档上都有很详细的说明,这里就不再赘述了。直接看代码。

ISourceGenerator只有两个方法

在本例中,Initialize注册了一个SyntaxReceiver,可以接收从引用项目中代码更改时,得到的Syntax Tree

接着在Execute方法中,进行代码的拼装组合(小声吐槽一句,字符串写代码太蛋疼了)

[Generator]
    public class LogGenerator : ISourceGenerator
    {
        internal class SyntaxReceiver : ISyntaxReceiver
        {
            internal List<ClassDeclarationSyntax> ClassSyntaxNodes { get; } = new List<ClassDeclarationSyntax>();
            public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
            {
                if (syntaxNode is ClassDeclarationSyntax @class)
                {
                    ClassSyntaxNodes.Add(@class);
                }
            }

        }
        public void Initialize(GeneratorInitializationContext context)
        {
            context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
        }

        public void Execute(GeneratorExecutionContext context)
        {
             if (!(context.SyntaxReceiver is SyntaxReceiver receiver))
                return;
        }

首先,我的 Aop 思路很简单,伪代码如下:第一版代码是把源代码直接拷贝一份在代理类中,这会造成一个问题,调试的时候,断点不会进入到源代码中。第二版代码在内部new一个原来的类,断点可以进入到源代码中。

// 第一版代码
// private Task internal接口方法()
// {
    //方法体很暴力,直接从 MethodDeclarationSyntax 类型的 Body.ToFullString()
// }

// 第二版代码 (删除)

public Task 接口方法()
{
    var context = .......;
    context.Proceed = () => {
       // 第一版代码
       // internal接口方法();
       proxy.接口方法();
    };
    interceptor.Invoke(context);
}

从 context 获得SyntaxReceiver之后,就是从SyntaxReceiver中的每个ClassDeclarationSyntax获取类的名称、命名空间、Usings、继承的接口、所有的方法以及方法的返回值、参数列表、方法体等等,进行字符串层面的代码的拼接。

拼接完成后,再调用 context 上的AddSource方法即可

string proxyClassTemplate = ".........";
context.AddSource($"{名称}Proxy.cs", proxyClassTemplate);

完整代码可以查看GithubLogAopCodeGenerator 项目

最后,强烈建议安装Syntax Visualizer在 VS 的工具管理中安装 .NET Compiler Platform SDK

可以查看类文件的语法树,方便从 context 的SyntaxNode找到自己需要的属性

1. 测试例子接口

using LogAopCodeGenerator;
using Project.AppCore.Aop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Project.AppCore.Services
{
    [Aspectable(AspectHandleType = typeof(LogAop))]
    public interface IForTest
    {
        [LogInfo(Action = "有Async,返回Task<T>", Module = "测试")]
        Task<int> ReturnTaskValueAsync();
        [LogInfo(Action = "无Async,返回Task<T>", Module = "测试")]
        Task<int> ReturnTaskValue();
        [LogInfo(Action = "有Async,返回Task", Module = "测试")]
        Task ReturnTaskAsync();
        [LogInfo(Action = "无Async,返回Task", Module = "测试")]
        Task ReturnTask();
        [LogInfo(Action = "同步无返回", Module = "测试")]
        void ReturnVoid();
        [LogInfo(Action = "异步有返回", Module = "测试")]
        int ReturnInt();
    }
}

2. 测试例子接口实现

using Project.AppCore.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Project.Services
{
    public class CodeGenTest : IForTest
    {
        public int ReturnInt()
        {
            return 0;
        }

        public Task ReturnTask()
        {
            return Task.CompletedTask;
        }

        public async Task ReturnTaskAsync()
        {
            await Task.CompletedTask;
        }

        public Task<int> ReturnTaskValue()
        {
            return Task.FromResult<int>(1);
        }

        public async Task<int> ReturnTaskValueAsync()
        {
            await Task.Delay(100);
            return 1;
        }

        public void ReturnVoid()
        {

        }
    }
}

3. 测试例子代理生成



using Project.AppCore.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using LogAopCodeGenerator;
namespace Project.Services
{
    public class CodeGenTestProxy: IForTest
    {            
         
        private Project.AppCore.Aop.LogAop logaop;

        private BaseContext[] contexts;
        private CodeGenTest proxy;
        public CodeGenTestProxy (Project.AppCore.Aop.LogAop logaop)
        {
            this.logaop = logaop;

            contexts = InitContext.InitTypesContext(typeof(CodeGenTest),typeof(IForTest));
            proxy = new CodeGenTest();
        }

        public int ReturnInt()
        {
            var baseContext = contexts.First(x => x.ImplementMethod.Name == "ReturnInt");    
            var context = InitContext.BuildContext(baseContext);
            context.Exected = false;
            context.HasReturnValue = true;
            context.Parameters = new object[] {  };
            int val = default;
            context.Proceed =  () => 
            {
                context.Exected = true;
                val =  proxy.ReturnInt();
                context.ReturnValue = val;
                return System.Threading.Tasks.Task.CompletedTask;
            };
             logaop.Invoke(context).Wait();
            return val;
        }

        public System.Threading.Tasks.Task ReturnTask()
        {
            var baseContext = contexts.First(x => x.ImplementMethod.Name == "ReturnTask");    
            var context = InitContext.BuildContext(baseContext);
            context.Exected = false;
            context.HasReturnValue = true;
            context.Parameters = new object[] {  };
            
            context.Proceed =  () => 
            {
                context.Exected = true;
                 proxy.ReturnTask().Wait();
                
                return System.Threading.Tasks.Task.CompletedTask;
            };
             logaop.Invoke(context).Wait();
            return System.Threading.Tasks.Task.CompletedTask;
        }

        public async System.Threading.Tasks.Task ReturnTaskAsync()
        {
            var baseContext = contexts.First(x => x.ImplementMethod.Name == "ReturnTaskAsync");    
            var context = InitContext.BuildContext(baseContext);
            context.Exected = false;
            context.HasReturnValue = true;
            context.Parameters = new object[] {  };
            
            context.Proceed =  async () => 
            {
                context.Exected = true;
                 await proxy.ReturnTaskAsync();
                
                
            };
             await logaop.Invoke(context);
            
        }

        public System.Threading.Tasks.Task<int> ReturnTaskValue()
        {
            var baseContext = contexts.First(x => x.ImplementMethod.Name == "ReturnTaskValue");    
            var context = InitContext.BuildContext(baseContext);
            context.Exected = false;
            context.HasReturnValue = true;
            context.Parameters = new object[] {  };
            int val = default;
            context.Proceed =  () => 
            {
                context.Exected = true;
                val =  proxy.ReturnTaskValue().Result;
                context.ReturnValue = val;
                return System.Threading.Tasks.Task.CompletedTask;
            };
             logaop.Invoke(context).Wait();
            return System.Threading.Tasks.Task.FromResult<int>(val);
        }

        public async System.Threading.Tasks.Task<int> ReturnTaskValueAsync()
        {
            var baseContext = contexts.First(x => x.ImplementMethod.Name == "ReturnTaskValueAsync");    
            var context = InitContext.BuildContext(baseContext);
            context.Exected = false;
            context.HasReturnValue = true;
            context.Parameters = new object[] {  };
            int val = default;
            context.Proceed =  async () => 
            {
                context.Exected = true;
                val =  await proxy.ReturnTaskValueAsync();
                context.ReturnValue = val;
                
            };
             await logaop.Invoke(context);
            return val;
        }

        public void ReturnVoid()
        {
            var baseContext = contexts.First(x => x.ImplementMethod.Name == "ReturnVoid");    
            var context = InitContext.BuildContext(baseContext);
            context.Exected = false;
            context.HasReturnValue = false;
            context.Parameters = new object[] {  };
            
            context.Proceed =  () => 
            {
                context.Exected = true;
                 proxy.ReturnVoid();
                
                return System.Threading.Tasks.Task.CompletedTask;
            };
             logaop.Invoke(context).Wait();
            
        }
     
    }
}


posted @ 2022-05-23 21:48  yaoqinglin_mtiter  阅读(323)  评论(0编辑  收藏  举报