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();
}
}
}