- A+
介绍
这个项目的名称“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