前言:
近期在学习微软基于dotnet5以上的SourceGenerator,使用该工具制作了一个比较简易的AOP工具,如有不正确的地方请大家斧正,谢谢
关于SourceGenerator
本文主要还是讲写代码的过程,历史渊源和其他基本使用可以参考其他博客,直接搜索SourceGenerator,相当多的内容,我也是学习前人的分享
几点注意事项
1、项目的*.csproj 文件内容建议:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>preview</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<PackageTags>Analyzer</PackageTags>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!--因为我自己的其他生成器使用了Newtonsoft.Json-->
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>
<!--要避免引用该生成器项目的项目出现引用Newtonsoft.Json失败的问题,需在此处写上这个引用添加-->
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
</Project>
2、如果还要生成NuGet包的话
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Authors>作者姓名</Authors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.0.1</Version>
<Deterministic>false</Deterministic>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Description>nuget包描述</Description>
<PackageTags>Analyzer</PackageTags>
<IncludeBuildOutput>false</IncludeBuildOutput>
<PackageIcon>包图标.ioc</PackageIcon>
<!--<PackageLicenseExpression>MIT</PackageLicenseExpression>-->
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<!--如有其他引用的包,也需要按照这样的格式-->
<TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
<!--最重要的-->
<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/donet/cs" Visible="false" />
</ItemGroup>
进入正文
1.准备工作
类名 | 介绍 |
---|---|
AopMethod | 用于储存方法、执行类、返回值、异常信息等,也可根据这个自行扩展 |
AopBaseAttribute | AOP特性基类(派生特性命名需以AOP结尾) |
GeneratorAopProxyAttribute | 标记在partial类上,用于生成存储当前类中的代理方法 |
1.1 文本格式的3个类内容,用于直接生成,方便调用
1.1.1 AopMethod.txt
// <auto-generated/>
#pragma warning disable
#nullable enable
using System;
using System.Reflection;
namespace {你自定义一个命名空间就行,最好3个生成的文件处于同一个命名空间}
{
/// <summary>
/// Aop传递方法类
/// </summary>
public class AopMethod
{
public AopMethod(object _TargetClass,MethodInfo method,params object[] para)
{
TargetClass=_TargetClass;
MethodTarget=method;
Paramters=para;
}
/// <summary>
/// 使用方法的类
/// </summary>
public object TargetClass {get; private set;}
/// <summary>
/// 方法主体
/// </summary>
public MethodInfo MethodTarget {get; private set;}
/// <summary>
/// 方法的参数
/// </summary>
public object[] Paramters {get; private set;}
/// <summary>
/// 方法的返回值
/// </summary>
public object ReturnValue {get; private set;}
/// <summary>
/// 执行方法
/// </summary>
public void Excute()
{
ReturnValue=MethodTarget.Invoke(TargetClass, Paramters);
}
}
}
1.1.2 AopBaseAttribute.txt
// <auto-generated/>
#pragma warning disable
#nullable enable
using System;
using System.Reflection;
namespace {你自定义一个命名空间就行,最好3个生成的文件处于同一个命名空间}
{
/// <summary>
/// AOP特性基类(派生特性命名需以AOP结尾)
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public abstract class AopBaseAttribute : Attribute
{
/// <summary>
/// 执行位置
/// </summary>
public InvokeLocation Location { set; get; } = InvokeLocation.Both;
/// <summary>
/// 方法执行前
/// </summary>
public abstract void ExcuteBefore();
/// <summary>
/// 方法执行中
/// </summary>
public abstract void ExcuteMiddle(AopMethod method);
/// <summary>
/// 方法执行后
/// </summary>
public abstract void ExcuteAfter(AopMethod method,Exception ex);
}
/// <summary>
/// 执行位置枚举
/// </summary>
public enum InvokeLocation
{
/// <summary>
/// 中间
/// </summary>
Both = 0,
/// <summary>
/// 执行前
/// </summary>
Before = 1,
/// <summary>
/// 执行后
/// </summary>
After = 2
}
}
1.1.3 GeneratorAopProxyAttribute.txt
// <auto-generated/>
#pragma warning disable
#nullable enable
using System;
namespace {你自定义一个命名空间就行,最好3个生成的文件处于同一个命名空间}
{
/// <summary>
/// 标记之后会生成一个Proxy代理类,配合AopBase的派生特性实现AOP功能
/// </summary>
[AttributeUsage( AttributeTargets.Class,Inherited=true,AllowMultiple=false)]
public class GeneratorAopProxyAttribute:Attribute
{
}
}
1.2 再准备2个帮助类(从前人那学习缝合来的)
1.2.1 Utils
/// <summary>
/// 工具类
/// </summary>
public static class Utils
{
#region 代码规范风格化
/// <summary>
/// 转换为Pascal风格-每一个单词的首字母大写
/// </summary>
/// <param name="fieldName">字段名</param>
/// <param name="fieldDelimiter">分隔符</param>
/// <returns></returns>
public static string ConvertToPascal(string fieldName, string fieldDelimiter)
{
string result = string.Empty;
if (fieldName.Contains(fieldDelimiter))
{
//全部小写
string[] array = fieldName.ToLower().Split(fieldDelimiter.ToCharArray());
foreach (var t in array)
{
//首字母大写
result += t.Substring(0, 1).ToUpper() + t.Substring(1);
}
}
else if (string.IsNullOrWhiteSpace(fieldName))
{
result = fieldName;
}
else if (fieldName.Length == 1)
{
result = fieldName.ToUpper();
}
else
{
result = fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1);
}
return result;
}
/// <summary>
/// 转换为Camel风格-第一个单词小写,其后每个单词首字母大写
/// </summary>
/// <param name="fieldName">字段名</param>
/// <param name="fieldDelimiter">分隔符</param>
/// <returns></returns>
public static string ConvertToCamel(string fieldName, string fieldDelimiter)
{
//先Pascal
string result = ConvertToPascal(fieldName, fieldDelimiter);
//然后首字母小写
if (result.Length == 1)
{
result = result.ToLower();
}
else
{
result = result.Substring(0, 1).ToLower() + result.Substring(1);
}
return result;
}
#endregion
/// <summary>
/// 获取模版文件并转成string
/// </summary>
/// <param name="FileName">文件名称</param>
/// <returns>string</returns>
internal static string GetTemplateByFileName(string FileRelativePath)
{
Assembly assembly = Assembly.GetExecutingAssembly();
string[] resNames = assembly.GetManifestResourceNames();
using (Stream stream = assembly.GetManifestResourceStream($"{assembly.GetName().Name}.Template.{FileRelativePath}"))
{
if (stream != null)
{
using (StreamReader sr = new StreamReader(stream))
{
string context = sr.ReadToEnd();
return context;
}
}
else
{
return "";
}
}
}
internal static string GetContentInParentheses(string value)
{
var match = Regex.Match(value, @"\(([^)]*)\)");
return match.Groups[1].Value;
}
}
1.2.2 SyntaxUtils
public class SyntaxUtils
{
public static bool HasModifier(MemberDeclarationSyntax syntax, params SyntaxKind[] modifiers)
{
return syntax.Modifiers.Any(m => modifiers.Contains(m.Kind()));
}
public static bool HasModifiers(MemberDeclarationSyntax syntax, params SyntaxKind[] modifiers)
{
var syntaxKinds = syntax.Modifiers.Select(n => n.Kind()).ToList();
return modifiers.All(m => syntaxKinds.Contains(m));
}
public static string GetName(SyntaxNode syntaxNode)
{
switch (syntaxNode)
{
case BaseTypeDeclarationSyntax baseTypeDeclarationSyntax:
return baseTypeDeclarationSyntax.Identifier.Text;
case BaseNamespaceDeclarationSyntax baseNamespaceDeclarationSyntax:
return baseNamespaceDeclarationSyntax.Name.ToString();
case VariableDeclaratorSyntax variableDeclaratorSyntax:
return variableDeclaratorSyntax.Identifier.Text;
case NameEqualsSyntax nameEqualsSyntax:
return nameEqualsSyntax.Name.Identifier.Text;
default:
throw new NotImplementedException();
}
}
public static bool HasAttribute(MemberDeclarationSyntax classDeclaration, Func<string, bool> func)
{
return GetAttribute(classDeclaration, func) != null;
}
public static bool HasAttribute(MemberDeclarationSyntax classDeclaration, string AttributeName)
{
var dto = GetAttribute(classDeclaration, AttributeName);
return dto != null;
}
public static AttributeSyntax GetAttribute(MemberDeclarationSyntax classDeclaration, Func<string, bool> func)
{
return classDeclaration.AttributeLists.SelectMany(m => m.Attributes)
.FirstOrDefault(m => func(m.Name.ToString()));
}
public static AttributeSyntax GetAttribute(MemberDeclarationSyntax classDeclaration, string AttributeName)
{
var attributes = classDeclaration.AttributeLists.SelectMany(m => m.Attributes);
var attr= attributes.FirstOrDefault(m =>
{
var nameSyntax = m.Name as IdentifierNameSyntax;
if (nameSyntax.Identifier.Text.Equals(AttributeName))
{
return true;
}
return false;
});
return attr;
}
/// <summary>
/// 检查是否包含AOP特性
/// </summary>
/// <param name="classDeclaration"></param>
/// <returns></returns>
public static bool HasAopAttribute(MemberDeclarationSyntax classDeclaration,out AttributeSyntax attribute)
{
attribute = GetAopAttribute(classDeclaration);
return attribute != null;
}
/// <summary>
/// 获取已AOP结尾的特性
/// </summary>
/// <param name="classDeclaration"></param>
/// <returns></returns>
public static AttributeSyntax GetAopAttribute(MemberDeclarationSyntax classDeclaration)
{
var attributes= classDeclaration.AttributeLists.SelectMany(m => m.Attributes);
var attr = attributes.FirstOrDefault(m =>
{
var nameSyntax = m.Name as IdentifierNameSyntax;
if (nameSyntax.Identifier.ValueText.EndsWith("AOP"))
{
return true;
}
return false;
});
return attr;
}
public static List<string> GetUsings(ClassDeclarationSyntax classDeclarationSyntax)
{
var compilationUnitSyntax = classDeclarationSyntax.SyntaxTree.GetRoot() as CompilationUnitSyntax;
var usings = compilationUnitSyntax.Usings.Select(m => m.ToString()).ToList();
return usings;
}
}
2.写生成器的代码
2.1 先写一个SyntaxReceiver,筛选出我们需要的内容
public class AopProxySyntaxReceiver : ISyntaxReceiver
{
//存储筛选出来标记了GeneratorAopProxyAttribute特性的类
public List<AttributeSyntax> CandidateClasses { get; } = new List<AttributeSyntax>();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is AttributeSyntax cds && cds.Name is IdentifierNameSyntax identifierName && identifierName.Identifier.ValueText == "GeneratorAopProxy")
{
CandidateClasses.Add(cds);
}
}
}
2.2 再写生成器代码
[Generator]//必须标记该特性,并实现该接口
public partial class AopProxyGenerator : ISourceGenerator
{
//初始化注册刚才的接收器
public void Initialize(GeneratorInitializationContext context)
{
#if DEBUG
//Debugger.Launch();
//使用这个方式,在编译引用该生成器的项目的时候,会进入断点,查看我们写的生成器运行情况
#endif
context.RegisterForSyntaxNotifications(() => new AopProxySyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
var syntaxReceiver = (AopProxySyntaxReceiver)context.SyntaxReceiver;
var attributeSyntaxList = syntaxReceiver.CandidateClasses;
if (attributeSyntaxList.Count == 0)
{
return;
}
List<string> ClassName = new List<string>();
foreach (AttributeSyntax attributeSyntax in attributeSyntaxList)
{
// 找到class,并且判断一下是否有parital字段
var classDeclarationSyntax = attributeSyntax.FirstAncestorOrSelf<ClassDeclarationSyntax>();
if (classDeclarationSyntax == null || !classDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
{
continue;
}
//判断是否已经处理过这个class了
if (ClassName.Contains(classDeclarationSyntax.Identifier.ValueText))
{
continue;
}
// 找到namespace
var namespaceDeclarationSyntax = classDeclarationSyntax.FirstAncestorOrSelf<BaseNamespaceDeclarationSyntax>();
// 找到methods
var methodDeclarationList = classDeclarationSyntax.Members.OfType<MethodDeclarationSyntax>().ToList();
if (methodDeclarationList.Count == 0)
{
continue;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine("using {引用你上文生成3个文件的那个命名空间};");
sb.AppendLine("using System.Reflection;");
sb.AppendLine();
sb.AppendLine($"namespace {namespaceDeclarationSyntax.Name}");
sb.AppendLine("{");
sb.AppendLine($" public partial class {classDeclarationSyntax.Identifier.Text}");
sb.AppendLine(" {");
foreach (MethodDeclarationSyntax methodDeclaration in methodDeclarationList)
{
//判断一下是否包含AOP结尾的特性
if (!SyntaxUtils.HasAopAttribute(methodDeclaration, out AttributeSyntax AopAttribute))
{
continue;
}
var AopAttributeName = (AopAttribute.Name as IdentifierNameSyntax).Identifier.ValueText;
//获取方法的返回值字符串
var ReturnTypeStr = (methodDeclaration.ReturnType as PredefinedTypeSyntax).Keyword.ValueText;
//获取方法内的参数
var ParameterList = methodDeclaration.ParameterList.Parameters;
//var asd= ParameterList.Select(s => $"{s.ToFullString().Split(' ')[1]}").ToList();
sb.AppendLine("/// <summary>");
sb.AppendLine($"/// {methodDeclaration.Identifier.Text}的AOP代理方法");
sb.AppendLine($"/// <para>多个AOP特性均会执行</para>");
sb.AppendLine("/// </summary>");
sb.AppendLine($"public {ReturnTypeStr} {methodDeclaration.Identifier.Text}_Proxy ({string.Join(",", ParameterList.Select(s => $"{s.ToFullString()}"))})");
sb.AppendLine("{");
if (ReturnTypeStr != "void")
{
sb.AppendLine($"return ({ReturnTypeStr})ExcuteProxy(\"{ methodDeclaration.Identifier.Text}\",{string.Join(",", ParameterList.Select(s => $"{s.ToFullString().Split(' ')[1]}"))});");
}
else
{
sb.AppendLine($"ExcuteProxy(\"{methodDeclaration.Identifier.Text}\",{string.Join(",", ParameterList.Select(s => $"{s.ToFullString().Split(' ')[1]}"))});");
}
sb.AppendLine("}");
}
sb.AppendLine("/// <summary>");
sb.AppendLine("/// 执行代理方法");
sb.AppendLine("/// </summary>");
sb.AppendLine($@"
private object ExcuteProxy(string MethodName,params object[] args)
{{
object result = null;
var method = this.GetType().GetMethod(MethodName);
object[]? attrs = method.GetCustomAttributes(true);
foreach (var item in attrs)
{{
if (item is AopBaseAttribute aop)
{{
AopMethod aopMethod = new AopMethod(this, method, args);
Exception ex = null;
try
{{
if (aop.Location == InvokeLocation.Before)
{{
aop.ExcuteBefore();
}}
if (aop.Location == InvokeLocation.Both)
{{
aop.ExcuteMiddle(aopMethod);
}}
}}
catch (Exception exx)
{{
ex=exx;
}}
if (aopMethod.ReturnValue is Task)
{{
(aopMethod.ReturnValue as Task).ContinueWith((Task t) =>
{{
aop.ExcuteAfter(aopMethod, t.Exception);
}});
}}
else
{{
aop.ExcuteAfter(aopMethod, ex);
}}
result = aopMethod.ReturnValue;
}}
}}
return result;
}}
");
sb.AppendLine(" }");
sb.AppendLine("}");
//使用这个方式格式化代码,免得生成的格式太难看
string extensionTextFormatted = CSharpSyntaxTree.ParseText(sb.ToString(), new CSharpParseOptions(LanguageVersion.CSharp8)).GetRoot().NormalizeWhitespace().SyntaxTree.GetText().ToString();
// 添加到源代码,这样IDE才能感知
context.AddSource($"{classDeclarationSyntax.Identifier}.g.cs", SourceText.From(extensionTextFormatted, Encoding.UTF8));
// 保存一下类名,避免重复生成
ClassName.Add(classDeclarationSyntax.Identifier.ValueText);
}
}
}
3. 如何使用
3.1 2种引用方式
3.1.1 直接工程内项目间引用
需在引用的那个项目的csproj文件中,添加:ReferenceOutputAssembly=“false” OutputItemType=“Analyzer” ,这样才会出现在分析器中
3.1.2 使用NuGet包引用,就没这么麻烦,不需要添加上面的
3.2 开始使用
3.2.1 新建一个特性,继承AopBaseAttribute,命名需XXAOPAttribute
public class testAOPAttribute : AopBaseAttribute
{
public override void ExcuteAfter(AopMethod method, Exception ex)
{
}
public override void ExcuteBefore()
{
}
public override void ExcuteMiddle(AopMethod method)
{
try
{
method.Excute();
throw new Exception("test");
}
catch (Exception ex)
{
throw ex;
}
}
}
3.2.2 新建一个业务类
[GeneratorAopProxy]
public partial class TestViewModel
{
public TestViewModel()
{
string result = haha_Proxy("11");
}
//标记了这个特性之后,SourceGenertor会自动在对应的partial类中生成代理方法,就像在构造函数中那样直接使用
[testAOP(Location = InvokeLocation.Both)]
public string haha(string asd)
{
return asd + "123";
}
[testAOP(Location = InvokeLocation.Before)]
public void testAOP(string a,int b,decimal c)
{
}
}
3.3 查看生成的类
可以看到,已经自动生成了
public partial class TestViewModel
{
/// <summary>
/// testAOP的AOP代理方法
/// <para>多个AOP特性均会执行</para>
/// </summary>
public void testAOP_Proxy(string a, int b, decimal c)
{
ExcuteProxy("testAOP", a, b, c);
}
/// <summary>
/// haha的AOP代理方法
/// <para>多个AOP特性均会执行</para>
/// </summary>
public string haha_Proxy(string asd)
{
return (string)ExcuteProxy("haha", asd);
}
/// <summary>
/// 执行代理方法
/// </summary>
private object ExcuteProxy(string MethodName, params object[] args)
{
object result = null;
var method = this.GetType().GetMethod(MethodName);
object[]? attrs = method.GetCustomAttributes(true);
foreach (var item in attrs)
{
if (item is AopBaseAttribute aop)
{
AopMethod aopMethod = new AopMethod(this, method, args);
Exception ex = null;
try
{
if (aop.Location == InvokeLocation.Before)
{
aop.ExcuteBefore();
}
if (aop.Location == InvokeLocation.Both)
{
aop.ExcuteMiddle(aopMethod);
}
}
catch (Exception exx)
{
ex = exx;
}
if (aopMethod.ReturnValue is Task)
{
(aopMethod.ReturnValue as Task).ContinueWith((Task t) =>
{
aop.ExcuteAfter(aopMethod, t.Exception);
});
}
else
{
aop.ExcuteAfter(aopMethod, ex);
}
result = aopMethod.ReturnValue;
}
}
return result;
}
}
结束
对于SourceGenerator的使用,还有很多很多的应用场景,希望能在后续的工作中,能切实的帮助到我们。
文中的代码可能不是那么准确,但是这部分的使用我已经实际测试过可以使用,如有更好的建议,可给我留言哈,让我学习和提高!