介紹
這個專案的名稱“Fody”來源於屬於織巢鳥科(Ploceidae)的小鳥(Fody),本身意義為編織。
核心Fody引擎的程式碼庫地址 :https://github.com/Fody/Fody
Github上是這樣介紹的:
Fody 是一個用於織制 .NET
程式集的可擴充套件工具。它允許在構建過程中作為一部分來操縱程式集的中間語言(IL),這需要大量的底層程式碼編寫。這些底層程式碼需要了解 MSBuild
和 Visual Studio
的 API
。Fody
透過可擴充套件的外掛模型試圖消除這些底層程式碼。這種技術非常強大,例如,可以將簡單屬性轉換為完整的 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注入到程式集中。
示例程式碼
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
執行過程。
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 Studio
的 NuGet
管理器中搜尋 Fody
相關包,會有更多的一些三方或者小眾的庫,依舊值得嘗試。
小結
從 Fody
實現原理上就能看出,這個庫強,很強,非常強。加上現在已有的非常之多的外掛,除了能夠提升開發效率之外,以在一定程度上實現一些難以實現的功能。強烈推薦大家學習使用。
連結
Fody官方Demo:https://github.com/Fody/FodyAddinSamples
工具 --- IL指令集解釋:https://niuery.com/post/61