使用基於Roslyn的編譯時AOP框架來解決.NET項目的代碼複用問題

理想的代碼優化方式

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

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

基於Rosyln的編譯時插入代碼

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

直到讀到了這篇文章 Introducing C# Source Generators,文中提供了一種新的解決方案,即通過RoslynSource 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。

即實現以下流程
image

使用Metalama實現以上流程

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

下面作一個簡單示例

  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

我的博客即將同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=3pts31zq5mckw

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