本文介紹如何用Metalama框架無侵入地為.NET項目添加編譯時AOP及代碼分析,以及動態生成方法 ...
使用基於Roslyn的編譯時AOP框架來解決.NET項目的代碼復用問題
Metalama簡介1. 不止是一個.NET跨平臺的編譯時AOP框架
Metalama簡介2.利用Aspect在編譯時進行消除重覆代碼
Metalama簡介3.自定義.NET項目中的代碼分析
Metalama中的Fabric可以做什麼
Fabric
通過修改項目、命名空間、類型來達到一些效果,這引起修改包括:添加Aspect
或添加代碼分析
使用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
。
使用Fabric添加代碼分析
上文中我們提到,我們可以通過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