使用基于Roslyn的编译时AOP框架来解决.NET项目的代码复用问题
Metalama简介1. 不止是一个.NET跨平台的编译时AOP框架
Metalama简介2.利用Aspect在编译时进行消除重复代码
Metalama简介3.自定义.NET项目中的代码分析
Fabric
通过修改项目、命名空间、类型来达到一些效果,这引起修改包括:添加Aspect
或添加代码分析
前文中我们写过一个简单的Aspect
:
public class LogAttribute : OverrideMethodAspect
{
public override dynamic? OverrideMethod()
{
Console.WriteLine(meta.Target.Method.ToDisplayString() + " 开始运行.");
var result = meta.Proceed();
Console.WriteLine(meta.Target.Method.ToDisplayString() + " 结束运行.");
return result;
}
}
当我们使用它时,我们要在对应的方法上添加这个Attribute
:
[Log]
private static int Add(int a, int b) //... ...
那么当我们有一个Aspect
要在项目中大量使用时,在每个方法上添加这个Aspect
当然是一种方法,但是这种方法有2个缺点:
- 包含大量的重复代码
[Log]
- 对于原代码的入侵性太强
此时我们就可以使用Fabric
为所有符合要求的方法添加指定的Aspect
:
internal class Fabric : ProjectFabric
{
// 这个是重写项目的Fabric中修改项目的方法
public override void AmendProject(IProjectAmender amender)
{
// 添加 LogAttribute 到符合规则的方法上
// 为名为 Add 且 private 的方法添加 LogAttribute
amender.WithTargetMembers(c =>
c.Types.SelectMany(t => t.Methods)
.Where(t =>
t.Name == "Add" &&
t.Accessibility == Metalama.Framework.Code.Accessibility.Private)
).AddAspect(t => new LogAttribute());
}
}
这样就可以在不入侵现有代码的情况下为指定的方法添加Aspect
。
上文中我们提到,我们可以通过Aspect
为代码添加代码分析,当我们要将一个包含(且仅包含)代码分析的Aspect
应用于一批代码时,当然我们可以按本文示例1
中的方法,直接使用Fabric
将包含代码分析的Aspect
应用于指定代码。
但还有另外一种方法,我们可以直接在Fabric
中定义应用于指定代码的代码分析。
下面示例,我们验证所有类中的私有字段必须符合 _camelCase
,并且使用一个NamespaceFabric
来实现:
namespace FabricCamelCaseDemo;
class Fabric : NamespaceFabric
{
private static readonly DiagnosticDefinition<string> _warning = new(
"DEMO04",
Severity.Warning,
"'{0}'必须使用驼峰命名法并以'_'开头");
// 这个是命名空间的Fabric中修改命名空间规则 的方法
public override void AmendNamespace(INamespaceAmender amender)
{
// 取所有非static 的private的字段,并添加代码分析
amender.WithTargetMembers(c =>
c.AllTypes.SelectMany(t=>t.Fields)
.Where(t => t.Accessibility == Accessibility.Private && !t.IsStatic
)
)
//preview 0.5.8之前为 RegisterFinalValidator
.Validate(this.FinalValidator);
}
private void FinalValidator(in DeclarationValidationContext context)
{
var fullname = context.Declaration.ToDisplayString();
var fieldName = fullname.Split('.').LastOrDefault();
if (fieldName!=null && (!fieldName.StartsWith("_") || !char.IsLower(fieldName[1])))
{
context.Diagnostics.Report(_warning.WithArguments(fieldName));
}
}
}
当然因为当前使用的是NamespaceFabric
所以该规则只应用于当前命名空间如,我们如果在另外一个命名空间中定义一个违反规则的字段的话,并不会有警告。
namespace FabricCamelCase;
internal class OtherNamespace
{
int count = 0;
int _total = 0;
public int Add()
{
count++;
_total++;
return count + _total;
}
}
使用TypeFabric为类型动态添加方法
开始前伪造一个需求,假设我有一个类AddUtils
专门处理加法操作,它里面应该有从2个到15个参数的Add方法15个(当然我知道,可以使用params
等方法实现,所以这里是个伪需求)。
最终效果为
public class AddUtils
{
public int Add2(int x1, int x2)
{
var result = 0;
result += x1;
result += x2;
return 2;
}
public int Add3(int x1, int x2, int x3)
{
var result = 0;
result += x1;
result += x2;
result += x3;
return 3;
}
// 以此类推... 下面省去若干方法
}
那么我们可以用Metalama
如此实现
using System.Reflection.Emit;
using Metalama.Framework.Aspects;
using Metalama.Framework.Fabrics;
public class AddUtils
{
private class Fabric : TypeFabric
{
// 实现的方法体
[Template]
public int MethodTemplate()
{
var num = (int) meta.Tags["nums"]!;
var result = 0;
foreach (var targetParameter in meta.Target.Parameters)
{
result += targetParameter.Value;
}
return num;
}
public override void AmendType(ITypeAmender amender)
{
for (var i = 2; i < 15; i++)
{
// 生成一个方法
var methodBuilder = amender.Advices.IntroduceMethod(
amender.Type,
nameof(this.MethodTemplate),
tags: new TagDictionary { ["nums"] = i });
// 方法名
methodBuilder.Name = "Add" + i;
// 添加参数
for (int parameterIndex = 1; parameterIndex <= i; parameterIndex++)
{
methodBuilder.AddParameter($"x{parameterIndex}", typeof(int));
}
}
}
}
}
引用
本章源代码:https://github.com/chsword/metalama-demo
Metalama官方文档: https://doc.metalama.net/
Metalama Nuget包: https://www.nuget.org/packages/Metalama.Framework/0.5.11-preview