IL編織器 --- Fody

来源:https://www.cnblogs.com/pandefu/archive/2023/10/19/17775991.html
-Advertisement-
Play Games

介紹 這個項目的名稱“Fody”來源於屬於織巢鳥科(Ploceidae)的小鳥(Fody),本身意義為編織。 核心Fody引擎的代碼庫地址 :https://github.com/Fody/Fody Github上是這樣介紹的: Fody 是一個用於織制 .NET 程式集的可擴展工具。它允許在構建過 ...


介紹

fodyIcon.png

這個項目的名稱“Fody”來源於屬於織巢鳥科(Ploceidae)的小鳥(Fody),本身意義為編織。

核心Fody引擎的代碼庫地址 :https://github.com/Fody/Fody

Github上是這樣介紹的:

Fody 是一個用於織制 .NET 程式集的可擴展工具。它允許在構建過程中作為一部分來操縱程式集的中間語言(IL),這需要大量的底層代碼編寫。這些底層代碼需要瞭解 MSBuildVisual StudioAPIFody 通過可擴展的插件模型試圖消除這些底層代碼。這種技術非常強大,例如,可以將簡單屬性轉換為完整的 INotifyPropertyChanged 實現,添加對空參數的檢查,添加方法計時,甚至使所有字元串比較都不區分大小寫。

Fody 處理的底層任務包括:

  • MSBuild 任務註入到構建流程中。
  • 解析程式集和 pdb 文件的位置。
  • 抽象了與 MSBuild 日誌記錄的複雜性。
  • 將程式集和 pdb 文件讀入 Mono.Cecil 對象模型中。
  • 根據需要重新應用強名稱。
  • 保存程式集和 pdb 文件。

Fody 使用 Mono.Cecil 和基於插件的方法在編譯時修改 .NET 程式集的中間語言(IL)。

  • 它不需要額外的安裝步驟來構建。
  • 屬性是可選的,具體取決於所使用的編織器。
  • 不需要部署運行時依賴項。

插件

從介紹就可以看出,理論上只要你想要,基於這個庫基本上能做任何事情。

所以基於該庫,誕生了非常非常多的插件庫,下麵簡單介紹及編寫Demo簡單使用

插件 描述 Github URL
Fody 編織.net程式集的可擴展工具 https://github.com/Fody/Fody
AutoProperties.Fody 這個外接程式為您提供了對自動屬性的擴展控制,比如直接訪問backing欄位或攔截getter和setter。 https://github.com/tom-englert/AutoProperties.Fody
PropertyChanged.Fody 將屬性通知添加到實現INotifyPropertyChanged的所有類。 https://github.com/Fody/PropertyChanged
InlineIL.Fody 在編譯時註入任意IL代碼。 https://github.com/ltrzesniewski/InlineIL.Fody
MethodDecorator.Fody 通過IL重寫編譯時間裝飾器模式 https://github.com/Fody/MethodDecorator
NullGuard.Fody 將空參數檢查添加到程式集 https://github.com/Fody/NullGuard
ToString.Fody 給屬性生成ToString()方法 https://github.com/Fody/ToString
Rougamo.Fody 在編譯時生效的AOP組件,類似於PostSharp。 https://github.com/inversionhourglass/Rougamo

AutoProperties.Fody

這個插件提供了對自動屬性的擴展控制,比如直接訪問backing欄位或攔截getter和setter。

using System;
using AutoProperties;
using Xunit;

public class AutoPropertiesInterceptor
{
    [Fact]
    public void Run()
    {
        Assert.Equal(10, Property1);
        Assert.Equal("11", Property2);

        Property1 = 42;

        Assert.Equal(45, Property1);
        Assert.Equal("11", Property2);

        Property2 = "44";

        Assert.Equal(45, Property1);
        Assert.Equal("47", Property2);
    }

    [GetInterceptor]
    T GetInterceptor<T>(string propertyName, T fieldValue)
    {
        return (T)Convert.ChangeType(Convert.ToInt32(fieldValue) + 1, typeof(T));
    }

    [SetInterceptor]
    void SetInterceptor<T>(T value, string propertyName, out T field)
    {
        field = (T)Convert.ChangeType(Convert.ToInt32(value) + 2, typeof(T));
    }

    public int Property1 { get; set; } = 7;

    public string Property2 { get; set; } = "8";
}

PropertyChanged.Fody

該插件在編譯時將INotifyPropertyChanged代碼註入屬性中:

using System.ComponentModel;
using System.Runtime.CompilerServices;
using AutoProperties;
using Xunit;

public class AutoPropertiesSample : INotifyPropertyChanged
{
    int numberOfPropertyChangedCalls;

    public string AutoProperty1 { get; set; }
    public string AutoProperty2 { get; set; }

    public AutoPropertiesSample()
    {
        AutoProperty2.SetBackingField("42");
    }

    [Fact]
    public void Run()
    {
        // no property changed call was generated in constructor:
        Assert.Equal(0, numberOfPropertyChangedCalls);
        Assert.Equal("42", AutoProperty2);

        AutoProperty1 = "Test1";
        Assert.Equal(1, numberOfPropertyChangedCalls);
        Assert.Equal("Test1", AutoProperty1);

        AutoProperty1.SetBackingField("Test2");
        Assert.Equal(1, numberOfPropertyChangedCalls);
        Assert.Equal("Test2", AutoProperty1);
    }


    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        numberOfPropertyChangedCalls += 1;

        PropertyChanged?.Invoke(this, new(propertyName));
    }
}


除此之外,該插件附帶了一個 C# 代碼生成器,只需將實現 INotifyPropertyChanged 介面或包含 [AddINotifyPropertyChangedInterface] 屬性的類標記為 partial,生成器將會自動添加必要的事件和事件觸發器。

可以通過項目文件中的屬性配置代碼生成器:

<PropertyGroup>
  <PropertyChangedAnalyzerConfiguration>
    <IsCodeGeneratorDisabled>false</IsCodeGeneratorDisabled>
    <EventInvokerName>OnPropertyChanged</EventInvokerName>
  </PropertyChangedAnalyzerConfiguration>
</PropertyGroup>

更多用法建議查看官方文檔。

InlineIL.Fody

該插件允許在編譯時將任意IL註入到程式集中。

image.png

示例代碼

using System;
using Xunit;
using static InlineIL.IL.Emit;
public class Sample
{
    [Fact]
    public void Run()
    {
        var item = new MyStruct
        {
            Int = 42,
            Guid = Guid.NewGuid()
        };

        ZeroInit.InitStruct(ref item);

        Assert.Equal(0, item.Int);
        Assert.Equal(Guid.Empty, item.Guid);
    }

    struct MyStruct
    {
        public int Int;
        public Guid Guid;
    }
}

public static class ZeroInit
{
    public static void InitStruct<T>(ref T value)
        where T : struct
    {
        Ldarg(nameof(value));

        Ldc_I4_0();

        Sizeof(typeof(T));

        Unaligned(1);

        Initblk();
    }
}

小技巧:這裡可以藉助ILDASM工具先生成想要的 IL 代碼,在按照 IL 代碼取編寫要註入的 C# 代碼,也可以參照我之前的文章工具 --- IL指令集解釋,理解 IL 執行過程。

image.png

MethodDecorator.Fody

通過IL重寫編譯時裝飾器模式。

定義攔截器屬性:

using System;
using System.Reflection;
using MethodDecorator.Fody.Interfaces;

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Module)]
public class InterceptorAttribute : Attribute, IMethodDecorator
{
    public void Init(object instance, MethodBase method, object[] args)
    {
    }

    public void OnEntry()
    {
        InterceptionRecorder.OnEntryCalled = true;
    }

    public void OnExit()
    {
        InterceptionRecorder.OnExitCalled = true;
    }

    public void OnException(Exception exception)
    {
        InterceptionRecorder.OnExceptionCalled = true;
    }
}

定義攔截記錄器

public static class InterceptionRecorder
{
    public static bool OnEntryCalled;
    public static bool OnExitCalled;
    public static bool OnExceptionCalled;

    public static void Clear()
    {
        OnExitCalled= OnEntryCalled = OnExceptionCalled = false;
    }
}

定義目標類

public static class Target
{
    [Interceptor]
    public static void MyMethod()
    {

    }

    [Interceptor]
    public static void MyExceptionMethod()
    {
        throw new("Foo");
    }
}

示例:

using Xunit;

public class MethodDecoratorSample
{
    [Fact]
    public void SimpleMethodSample()
    {
        InterceptionRecorder.Clear();
        Target.MyMethod();
        Assert.True(InterceptionRecorder.OnEntryCalled);
        Assert.True(InterceptionRecorder.OnExitCalled);
        Assert.False(InterceptionRecorder.OnExceptionCalled);
    }

    [Fact]
    public void ExceptionMethodSample()
    {
        InterceptionRecorder.Clear();
        try
        {
            Target.MyExceptionMethod();
        }
        catch
        {
        }
        Assert.True(InterceptionRecorder.OnEntryCalled);
        Assert.False(InterceptionRecorder.OnExitCalled);
        Assert.True(InterceptionRecorder.OnExceptionCalled);
    }
}

NullGuard.Fody

該插件向程式集添加null參數檢查,支持三種操作模式:隱式模式顯式模式可為空引用類型模式

  • 在隱式模式下,假定一切都不為空,除非標記為 [AllowNull]。這是 NullGuard 一直以來的工作方式。
  • 在顯式模式下,假定一切都可為空,除非標記為 [NotNull]。這種模式旨在支持 ReSharper(R#)的可為空性分析,使用悲觀模式。
  • 在可為空引用類型模式下,使用 C# 8 可為空引用類型(NRT)註釋來確定類型是否可為空。

如果沒有顯式配置,NullGuard 將按以下方式自動檢測模式:

  • 如果檢測到 C# 8 可為空屬性,則使用可為空引用類型模式。
  • 引用 JetBrains.Annotations 併在任何地方使用 [NotNull] 將切換到顯式模式。
  • 如果不滿足上述條件,則預設為隱式模式。

示例:

using Xunit;

public class NullGuardSample
{
    [Fact(Skip = "Explicit")]
    public void Run()
    {
        var targetClass = new TargetClass();
        Assert.Throws<ArgumentNullException>(() => targetClass.Method(null));
    }
}

public class TargetClass
{
    public void Method(string param)
    {
    }
}

ToString.Fody

該插件可以從帶有[ToString]屬性修飾的類的公共屬性中生成ToString方法。

using System.Diagnostics;
using Xunit;

public class ToStringSample
{
    [Fact]
    public void Run()
    {
        var target = new Person
                     {
                         GivenNames = "John",
                         FamilyName = "Smith"

                     };
        Debug.WriteLine(target.ToString());
        Assert.Equal("{T: \"Person\", GivenNames: \"John\", FamilyName: \"Smith\"}", target.ToString());
    }
}

[ToString]
class Person
{
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }

    [IgnoreDuringToString]
    public string FullName => $"{GivenNames} {FamilyName}";
}

Rougamo.Fody

Rougamo是一個靜態代碼織入的AOP組件,類似Postsharp的一個組件,具有 MethodDecorator.Fody的功能,但功能更加強大,我個人覺得最為突出,優秀的兩個功能點:

  • 匹配
  • 編織

匹配指的是命中AOP要攔截的目標匹配,比如有特征匹配,表達式匹配,類型匹配,更細化到模糊匹配,正則匹配。

編製則指的是攔截後能做的操作,比如有重寫方法參數,修改返回值,異常處理,重試等。

該插件很強大,示例代碼太多,就不再本篇內列出示例代碼,官方文檔中文介紹非常詳細,建議直接查看官方文檔。

其他

在Github庫中,它提供了一些插件使用的Demo,除以上簡單介紹的部分插件以外,還有這些

<Weavers VerifyAssembly="true"
         VerifyIgnoreCodes="0x80131869"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
 
  <Anotar.Catel />
  <Anotar.Splat />
  <Anotar.Serilog />
  <Anotar.NLog />
  <Anotar.Custom />
  <Anotar.CommonLogging />
  <AsyncErrorHandler />
  <BasicFodyAddin />
  <Caseless />
  <ConfigureAwait  ContinueOnCapturedContext="false" />
  <EmptyConstructor />
  <ExtraConstraints />
  <Equatable />
  <InfoOf />
  <Ionad />
  <Janitor />
  <MethodTimer />
  <ModuleInit />
  <Obsolete />
  <PropertyChanging />
  <PropertyChanged />
  <Validar />
  <Resourcer />
  <Publicize />
  <Virtuosity />
  <Visualize />
</Weavers>

若是在 Visual StudioNuGet 管理器中搜索 Fody 相關包,會有更多的一些三方或者小眾的庫,依舊值得嘗試。

小結

Fody 實現原理上就能看出,這個庫很強非常強。加上現在已有的非常之多的插件,除了能夠提升開發效率之外,以在一定程度上實現一些難以實現的功能。強烈推薦大家學習使用。

鏈接

Fody官方Demo:https://github.com/Fody/FodyAddinSamples

工具 --- IL指令集解釋:https://niuery.com/post/61

作者: Niuery Daily

出處: https://www.cnblogs.com/pandefu/>

郵箱: [email protected]

關於作者:.Net Framework,.Net Core ,WindowsForm,WPF ,控制項庫,多線程

本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出 原文鏈接,否則保留追究法律責任的權利。 如有問題, 可郵件咨詢。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 創建名為spring_mvc_rest的新module,過程參考5.2節和6.6節 7.1、簡介 RESTful 也稱為REST(英文:Representational State Transfer)即表現層狀態傳遞,它是一種軟體架構風格或設計風格; REST 是 Roy Fielding 博士( ...
  • 將PDF轉為圖片能方便我們將文檔內容上傳至社交媒體平臺進行分享。此外,轉換為圖片後,還可以對圖像進行進一步的裁剪、調整大小或添加標記等操作。 用Python將PDF文件轉JPG/ PNG圖片可能是大家在一些項目中會遇到的需求,下麵將詳細介紹如何使用第三方庫Spire.PDF for Python來實 ...
  • 如何保持數據一致性 資料庫和緩存(比如:redis)雙寫數據一致性問題,是一個跟開發語言無關的公共問題。尤其在高併發的場景下,這個問題變得更加嚴重。 以下是我無意間瞭解很好的文章,分享給大家。 1. 常見方案 通常情況下,我們使用緩存的主要目的是為了提升查詢的性能。大多數情況下,我們是這樣使用緩存的 ...
  • CompletableFuture非同步編排優化代碼 我們在項目開發中,有可能遇到一個介面需要調用N個服務的介面。比如用戶請求獲取訂單信息,需要調用用戶信息、商品信息、物流信息等介面,最後再彙總數據統一返回。如果使用串列的方法按照順序挨個調用介面,這樣介面的響應的速度就很慢。如果並行調用介面,同時調用 ...
  • 本文分享自華為雲社區《從入門到精通:SimpleDateFormat類高深用法,讓你的代碼更簡潔!》,作者:bug菌。 環境說明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8 @[toc] 前言 日期時間在開發中是非常常見的需求,尤其是在處理與時間相關的 ...
  • 鏈表(Linked List)是一種線性數據結構,它由一系列節點(Node)組成,每個節點包含兩部分:數據和指向下(上)一個節點的引用(或指針)。鏈表中的節點按照線性順序連接在一起(相鄰節點不需要存儲在連續記憶體位置),不像數組一樣存儲在連續的記憶體位置。鏈表通常由頭節點(Head)來表示整個鏈表,而尾... ...
  • Ping 使用 `Internet` 控制消息協議(`ICMP`)來測試主機之間的連接。當用戶發送一個 `ping` 請求時,則對應的發送一個 `ICMP Echo` 請求消息到目標主機,並等待目標主機回覆一個 `ICMP Echo` 回應消息。如果目標主機接收到請求並且網路連接正常,則會返回一個回... ...
  • 我們每天都在使用著文件伺服器,那你知道他其中有多少功能嗎?壓縮功能在其中占了多大的作用嗎?瀏覽器又是如何的正確識別文件的功能? ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...