1. 程式人生 > 其它 >.NET中利用基於編譯時的AOP框架進行自動插入程式碼及優化重複程式碼

.NET中利用基於編譯時的AOP框架進行自動插入程式碼及優化重複程式碼

理想的程式碼優化方式

團隊日常協作中,自然而然的會出現很多重複程式碼,根據這些程式碼的種類,之前可能會以以下方式處理

方式 描述 應用時可能產生的問題
硬編碼 多數新手,或逐漸腐壞的專案會這麼幹,會直接複製之前實現的程式碼 帶來的問題顯而易見的多,例如架構會逐漸隨時間被侵蝕,例外越來越多
提取函式 提取成為函式,然後複用 提取函式,然後複用,會比直接硬編碼好些,但是仍然存在大量因“例外”而導致增加引數、增加函式過載的情況
模板生成器 CodeSmith/T4等 因為是獨立程序,所以對於讀取使用者程式碼或專案,實現難度較高,且需要現有使用者專案先生成成功,再進行生成 ,或者是完全基於新專案
AOP框架 面向切面程式設計,可以解決很多於使用者程式碼前後增加操作的事情 但是大多AOP框架都是基於透明代理形式實現的,對於相互呼叫較多的程式碼,但形成效能壓力,而且因為要符合透明代理的規則,所以要提供相應的子類或介面。

基於Rosyln的編譯時插入程式碼

但以上這幾種,AOP算是最理想的方式,但是感覺上還可以有更好的解決方案。

直到讀到了這篇文章文章 Introducing C# Source Generators,文中提供了一種新的解決方案,即通過Roslyn的Source Generator在編譯時直接讀取當前專案中的語法樹,處理並生成的新程式碼,然後在編譯時也使用這些新程式碼。

那麼如果可以讀取現有程式碼的語法樹,通過讀取程式碼中的標記,那麼在程式碼生成過程中是否就能直接生成。
實現如下效果:
專案中的原始碼 Program.cs

internal class Program
{
    [Log]
    private static int Add( int a, int b )
    {
        return a + b;
    }
}

自動根據 LogAttribute 自動編譯成的程式碼 Program.g.cs

internal class Program
{
    [Log]
    private static int Add( int a, int b )
    {
        Console.WriteLine("Program.Add(int, int) 開始執行.");
        int result;
        result = a + b;
        Console.WriteLine("Program.Add(int, int) 結束執行.");
        return result;
    }
}

當然LogAttribute中需要去實現插入程式碼。
然後專案自動使用新生成的Program.g.cs進行編譯。這樣就實現了基於編譯時的AOP。

即實現以下流程

使用Metalama實現以上流程

經過尋找,發現其實已經有框架可以實現我上面說的流程了,也就是在編譯時實現程式碼的插入。
https://www.postsharp.net/metalama 是 postsharp 的後續作品。

下面作一個簡單示例

  1. 建立一個.NET6.0的控制檯應用,我這裡命名為LogDemo,
    其中的入口檔案Program.cs
namespace LogDemo {
    public class Program
    {
        public static void Main(string[] args)
        {
            var r = Add(1, 2);
            Console.WriteLine(r);
        }
		// 這裡寫一個簡單的方法,一會對這個方法進行程式碼的插入
        private static int Add(int a, int b)
        {
            var result = a + b;
            Console.WriteLine("Add" + result);
            return result;
        }
    }
}
  1. 在專案中使用Metalama

通過引用包 https://www.nuget.org/packages/Metalama.Framework, 注意Metalama當前是Preview版本,如果通過視覺化Nuget管理器引入,需要注意勾選包含預發行版

dotnet add package Metalama.Framework --version 0.5.7-preview
  1. 編寫一個AOP的Attribute

在專案中引入 Metalama.Framework後無需多餘配置或程式碼,直接編寫一個AOP的Attribute

using Metalama.Framework.Aspects;

namespace LogDemo {
    public class Program
    {
        public static void Main(string[] args)
        {
            var r = Add(1, 2);
            Console.WriteLine(r);
        }
        // 在這個方法中使用了下面的Attribute
        [LogAttribute]
        private static int Add(int a, int b)
        {
            var result = a + b;
            Console.WriteLine("Add" + result);
            return result;
        }
    }
    // 這裡是增加的 Attribute
    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;

        }
    }
}
  1. 執行結果如下
Program.Add(int, int) 開始執行.
Add3
Program.Add(int, int) 結束執行.
3
  1. 生成的程式集進行反編譯,得到的程式碼如下:
using Metalama.Framework.Aspects;
namespace LogDemo {
    public class Program
    {
        public static void Main(string[] args)
        {
            var r = Add(1, 2);
            Console.WriteLine(r);
        }
        // 在這個方法中使用了下面的Attribute
        [LogAttribute]
        private static int Add(int a, int b)
        {
            Console.WriteLine("Program.Add(int, int) 開始執行.");
            int result_1;
            var result = a + b;
            Console.WriteLine("Add" + result);
            result_1 = result;
            Console.WriteLine("Program.Add(int, int) 結束執行.");
            return result_1;
        }
    }
#pragma warning disable CS0067
    // 這裡是增加的 Attribute
    public class LogAttribute : OverrideMethodAspect
    {
        public override dynamic? OverrideMethod() => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");
    }
#pragma warning restore CS0067
}

總結

這樣就完全實現了我之前想要的效果,當然,使用Metalama還可以實現很多能極大地提高生產力的功能,它不僅可以對方法進行改寫,也可以對屬性、欄位、事件、甚至是類、名稱空間進行改寫。

引用

Introducing C# Source Generators:https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/
Metalama官網:https://www.postsharp.net/metalama