Metalama簡介4.使用Fabric操作項目或命名空間

使用基於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個缺點:

  1. 包含大量的重複代碼[Log]
  2. 對於原代碼的入侵性太強

此時我們就可以使用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));
        }
    }
}

image

當然因爲當前使用的是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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章