SourceGenerator 使用姿勢(1):生成代理類,實現簡單的AOP

来源:https://www.cnblogs.com/ad313/archive/2022/12/08/16965418.html
-Advertisement-
Play Games

一:背景 1. 簡介 .NET 高級調試要想玩的好,看懂彙編是基本功,但看懂彙編和能寫點彙編又完全是兩回事,所以有時候看的多,總手癢癢想寫一點,在 Windows 平臺上搭建彙編環境不是那麼容易,大多還是用微軟的 MASM + DosBox 搭一個 8086 的環境,這玩意距今快 50 年了。 在以 ...


SourceGenerator 已經出來很久了,也一直在關註。之前觀摩大佬 @xljiulangxljiulang 的 WebApiClient 使用 SourceGenerator 生成介面代理類,深受啟發,準備拿過來用看看(發出白嫖的聲音),寫個編譯期靜態代理AOP。本篇重點是怎麼獲取元數據,得到想要的數據,生成想要的代碼(往下拖到第 4 點)。

幾個月前寫了個demo,現在趁著有空重新整理完善了下。.net 6 新增了個 IIncrementalGenerator 進行增量編譯,這個還沒研究,後面再說。

我的思路是繼承,生成一個類去繼承需要攔截的實際類,然後重寫相關的方法,此時插入額外的方法,比如 Before,After 等。這就要求相關方法必須是 可重寫 的, virtualoverride。好了,開乾。

1、定義Aop屬性,打個標簽,SourceGenerator 根據這個標簽查找相關的 class 或 interface

 1     /// <summary>
 2     /// Aop 攔截器
 3     /// </summary>
 4     public interface IAopInterceptor
 5     {
 6         /// <summary>
 7         /// 執行前操作,同步方法調用
 8         /// </summary>
 9         /// <param name="context"></param>
10         /// <returns></returns>
11         AopContext Before(AopContext context);
12         /// <summary>
13         /// 執行前操作,非同步方法調用
14         /// </summary>
15         /// <param name="context"></param>
16         /// <returns></returns>
17         ValueTask<AopContext> BeforeAsync(AopContext context);
18         /// <summary>
19         /// 執行後操作,同步方法調用
20         /// </summary>
21         /// <param name="context"></param>
22         /// <returns></returns>
23         AopContext After(AopContext context);
24         /// <summary>
25         /// 執行後操作,非同步方法調用
26         /// </summary>
27         /// <param name="context"></param>
28         /// <returns></returns>
29         ValueTask<AopContext> AfterAsync(AopContext context);
30         /// <summary>
31         /// 執行方法,同步方法調用
32         /// </summary>
33         /// <param name="context"></param>
34         /// <returns></returns>
35         AopContext Next(AopContext context);
36         /// <summary>
37         /// 執行方法,非同步方法調用
38         /// </summary>
39         /// <param name="context"></param>
40         /// <returns></returns>
41         ValueTask<AopContext> NextAsync(AopContext context);
42     }

可以不要 IAopInterceptor 這個介面,這裡加了只是為了約束。

 1     public class AopInterceptor : Attribute, IAopInterceptor
 2     {
 3         /// <summary>
 4         /// 是否執行 Before
 5         /// </summary>
 6         public bool HasBefore { get; set; }
 7         /// <summary>
 8         /// 是否執行 After
 9         /// </summary>
10         public bool HasAfter { get; set; }
11         /// <summary>
12         /// 是否執行 Aop 的 Next
13         /// </summary>
14         public bool HasAopNext { get; set; }
15         /// <summary>
16         /// 是否執行實際的方法
17         /// </summary>
18         public bool HasActualNext { get; set; }
19 
20         /// <summary>
21         /// 預設執行所以方法
22         /// </summary>
23         public AopInterceptor()
24         {
25             HasBefore = true;
26             HasAopNext = true;
27             HasActualNext = true;
28             HasAfter = true;
29         }
30 
31         public virtual AopContext Before(AopContext context) => context;
32 
33         public virtual async ValueTask<AopContext> BeforeAsync(AopContext context)
34         {
35             await ValueTask.CompletedTask;
36             return context;
37         }
38 
39         public virtual AopContext After(AopContext context)
40         {
41             return context.Exception != null ? throw context.Exception : context;
42         }
43 
44         public virtual async ValueTask<AopContext> AfterAsync(AopContext context)
45         {
46             if (context.Exception != null)
47                 throw context.Exception;
48 
49             await ValueTask.CompletedTask;
50             return context;
51         }
52 
53         public virtual AopContext Next(AopContext context)
54         {
55             try
56             {
57                 context.Invoke();
58             }
59             catch (Exception e)
60             {
61                 context.Exception = e;
62             }
63             return context;
64         }
65 
66         public virtual async ValueTask<AopContext> NextAsync(AopContext context)
67         {
68             try
69             {
70                 context = await context.InvokeAsync();
71             }
72             catch (Exception e)
73             {
74                 context.Exception = e;
75             }
76 
77             return context;
78         }
79     }
View Code

 

2、定義上下文,主要包含 是否是非同步,是否有返回值,還有實際方法的委托。決定了調用實際方法的時候怎麼調用

 1     /// <summary>
 2     /// Aop 上下文
 3     /// </summary>
 4     public struct AopContext
 5     {
 6         /// <summary>
 7         /// 是否是非同步
 8         /// </summary>
 9         public bool IsTask { get; private set; }
10         /// <summary>
11         /// 是否有返回值
12         /// </summary>
13         public bool HasReturnValue { get; private set; }
14         /// <summary>
15         /// 方法輸入參數
16         /// </summary>
17         public Dictionary<string, dynamic> MethodInputParam { get; private set; }
18 
19         /// <summary>
20         /// 實際方法執行結果,可能是 Task
21         /// </summary>
22         public Func<dynamic> ActualMethod { get; set; }
23         /// <summary>
24         /// 返回值,具體的值
25         /// </summary>
26         public dynamic ReturnValue { get; set; }
27         /// <summary>
28         /// 異常信息
29         /// </summary>
30         public Exception Exception { get; set; }
31         /// <summary>
32         /// IServiceProvider
33         /// </summary>
34         public IServiceProvider ServiceProvider { get; private set; }
35 
36         /// <summary>
37         /// 初始化
38         /// </summary>
39         /// <param name="serviceProvider"></param>
40         /// <param name="methodInputParam"></param>
41         /// <param name="isTask"></param>
42         /// <param name="hasReturnValue"></param>
43         /// <param name="actualMethod"></param>
44         public AopContext(IServiceProvider serviceProvider, Dictionary<string, dynamic> methodInputParam, bool isTask, bool hasReturnValue, Func<dynamic> actualMethod) : this()
45         {
46             ServiceProvider = serviceProvider;
47             MethodInputParam = methodInputParam;
48             IsTask = isTask;
49             HasReturnValue = hasReturnValue;
50             ActualMethod = actualMethod;
51         }
52 
53         /// <summary>
54         /// 執行實際方法 非同步
55         /// </summary>
56         /// <returns></returns>
57         public async ValueTask<AopContext> InvokeAsync()
58         {
59             if (ActualMethod == null)
60                 return this;
61 
62             if (HasReturnValue)
63             {
64                 ReturnValue = await ActualMethod();
65                 return this;
66             }
67 
68             await ActualMethod();
69             return this;
70         }
71 
72         /// <summary>
73         /// 執行實際方法 同步
74         /// </summary>
75         /// <returns></returns>
76         public void Invoke()
77         {
78             if (ActualMethod == null) 
79                 return;
80 
81             //特殊處理 同步且沒有返回值,用 Task.Run 包裝
82             if (!IsTask && !HasReturnValue)
83                 ActualMethod.Invoke().GetAwaiter().GetResult();
84             else
85                 ReturnValue = ActualMethod.Invoke();
86         }
87     }

3、硬編碼實現類

3.1、定義攔截器

 1     /// <summary>
 2     /// 常規服務,執行所有方法
 3     /// </summary>
 4     public class SampleAttribute : AopInterceptor
 5     {
 6         /// <summary>執行前操作,同步方法調用</summary>
 7         /// <param name="context"></param>
 8         /// <returns></returns>
 9         public override AopContext Before(AopContext context)
10         {
11             Console.WriteLine("Before...");
12             return base.Before(context);
13         }
14 
15         /// <summary>執行前操作,非同步方法調用</summary>
16         /// <param name="context"></param>
17         /// <returns></returns>
18         public override ValueTask<AopContext> BeforeAsync(AopContext context)
19         {
20             Console.WriteLine("BeforeAsync...");
21             return base.BeforeAsync(context);
22         }
23 
24         public override AopContext After(AopContext context)
25         {
26             Console.WriteLine("After...");
27             return context;
28         }
29 
30         /// <summary>執行後操作,非同步方法調用</summary>
31         /// <param name="context"></param>
32         /// <returns></returns>
33         public override ValueTask<AopContext> AfterAsync(AopContext context)
34         {
35             Console.WriteLine("AfterAsync...");
36             return base.AfterAsync(context);
37         }
38 
39         /// <summary>執行方法,同步方法調用</summary>
40         /// <param name="context"></param>
41         /// <returns></returns>
42         public override AopContext Next(AopContext context)
43         {
44             Console.WriteLine("Next...");
45             return base.Next(context);
46         }
47 
48         /// <summary>執行方法,非同步方法調用</summary>
49         /// <param name="context"></param>
50         /// <returns></returns>
51         public override ValueTask<AopContext> NextAsync(AopContext context)
52         {
53             Console.WriteLine("NextAsync...");
54             return base.NextAsync(context);
55         }
56     }
View Code

定義介面

1     public interface ITestService
2     {
3         [Sample]
4         DateTime SampleSync();
5 
6         [Sample]
7         ValueTask<DateTime> SampleAsync();
8     }

 

3.2、定義實現類

 1     public class TestService : ITestService
 2     {
 3 
 4         public virtual DateTime SampleSync()
 5         {
 6             return DateTime.Now;
 7         }
 8 
 9         public virtual async ValueTask<DateTime> SampleAsync()
10         {
11             await ValueTask.CompletedTask;
12             return DateTime.Now;
13         }
14     }

 

3.3、定義繼承類,重寫相關方法

 1     public sealed class TestService_Aop : TestService
 2     {
 3         private readonly IServiceProvider _serviceProvider0;
 4         public TestService_Aop(IServiceProvider serviceProvider0)
 5         {
 6             _serviceProvider0 = serviceProvider0;
 7         }
 8 
 9         public override DateTime SampleSync()
10         {
11             var aopContext = new AopContext(_serviceProvider0,
12                 new Dictionary<string, dynamic>() { },
13                 false,
14                 true,
15                 null);
16 
17             var aopInterceptor0 = _serviceProvider0.GetRequiredService<SampleAttribute>();
18             if (aopInterceptor0.HasBefore) aopContext = aopInterceptor0.Before(aopContext);
19             if (aopInterceptor0.HasAopNext)
20             {
21                 if (aopInterceptor0.HasActualNext)
22                 {
23                     aopContext.ActualMethod = () => base.SampleSync();
24                 }
25                 aopContext = aopInterceptor0.Next(aopContext);
26             }
27             else
28             {
29                 if (aopInterceptor0.HasActualNext)
30                 {
31                     aopContext.ReturnValue = base.SampleSync();
32                 }
33             }
34             if (aopInterceptor0.HasAfter) aopContext = aopInterceptor0.After(aopContext);
35 
36             return aopContext.ReturnValue;
37         }
38 
39         public override async ValueTask<DateTime> SampleAsync()
40         {
41             var aopContext = new AopContext(_serviceProvider0,
42                 new Dictionary<string, dynamic>() { },
43                 true,
44                 true,
45                 null);
46 
47             var aopInterceptor0 = _serviceProvider0.GetRequiredService<SampleAttribute>();
48             if (aopInterceptor0.HasBefore) aopContext = await aopInterceptor0.BeforeAsync(aopContext);
49             if (aopInterceptor0.HasAopNext)
50             {
51                 if (aopInterceptor0.HasActualNext)
52                 {
53                     aopContext.ActualMethod = () => base.SampleAsync();
54                 }
55                 aopContext = await aopInterceptor0.NextAsync(aopContext);
56             }
57             else
58             {
59                 if (aopInterceptor0.HasActualNext)
60                 {
61                     aopContext.ReturnValue = await base.SampleAsync();
62                 }
63             }
64             if (aopInterceptor0.HasAfter) aopContext = await aopInterceptor0.AfterAsync(aopContext);
65 
66             return aopContext.ReturnValue;
67         }
68     }

 

4、開整

4.1、新建項目 Mic.Aop.Generator,TargetFramework 選 netstandard2.0,引入兩個分析器包

<ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" PrivateAssets="all" />
        <
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原文:【開源庫推薦】 #4 Poi-辦公文檔處理庫 - Stars-One的雜貨小窩 github倉庫apache/poi Apache POI是Apache軟體基金會的開放源碼函式庫,POI提供API給Java程式對Microsoft Office格式檔案讀和寫的功能。.NET的開發人員則可以利用 ...
  • 1. 過期 key 處理 Redis 之所以性能強,最主要的原因就是基於記憶體存儲。然而單節點的 Redis 其記憶體大小不宜過大,會影響持久化或主從同步性能。 我們可以通過修改配置文件來設置 Redis 的最大記憶體: maxmemory 1gb 當記憶體使用達到上限時,就無法存儲更多數據了。為瞭解決這個 ...
  • laravel-route-notes laravel框架擴展,原生註解生成路由 優點是直接生成路由文件,不在運行中解析路由,提升效率 使用環境 [PHP] >= 8.0 [Laravel] >= 9.0 如何安裝 直接使用composer進行安裝: composer require --dev l ...
  • JZ36 二叉搜索樹與雙向鏈表 描述 輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表 註意: 1.要求不能創建任何新的結點,只能調整樹中結點指針的指向。當轉化完成以後,樹中節點的左指針需要指向前驅,樹中節點的右指針需要指向後繼 2.返回鏈表中的第一個節點的指針 3.函數返回的TreeNo ...
  • 類模版std::function是一種通用、多態的函數封裝。std::function的實例可以對任何可以調用的目標實體進行存儲、複製、和調用操作,這些目標實體包括普通函數、Lambda表達式、函數指針、以及其它函數對象等。std::function對象是對C++中現有的可調用實體的一種類型安全的包 ...
  • Listener記憶體馬 0x01Lintener機制分析 Java Web 開發中的監聽器(Listener)就是 Application、Session 和 Request 三大對象創建、銷毀或者往其中添加、修改、刪除屬性時自動執行代碼的功能組件。 Listener 三個域對象 ServletCo ...
  • 就像黑火藥時代里突然誕生的核彈一樣,OpenAI的ChatGPT語言模型的橫空出世,是人工智慧技術發展史上的一個重要里程碑。這是一款無與倫比、超凡絕倫的模型,能夠進行自然語言推理和對話,並且具有出色的語言生成能力。 ...
  • 前言 之所以會搞這個手勢識別分類,其實是為了滿足之前群友提的需求,就是針對稚暉君的ElectronBot機器人的上位機軟體的功能豐富,因為本來擅長的技術棧都是.NET,也剛好試試全能的.NET是不是真的全能就想著做下試試了,MediaPipe作為谷歌開源的機器視覺庫,功能很豐富了,而且也支持c++, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...