IL编织器 — Fody

  • IL编织器 — Fody已关闭评论
  • 103 次浏览
  • A+
所属分类:.NET技术
摘要

这个项目的名称“Fody”来源于属于织巢鸟科(Ploceidae)的小鸟(Fody),本身意义为编织。


介绍

IL编织器 --- Fody

这个项目的名称“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注入到程序集中。

IL编织器 --- Fody

示例代码

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 执行过程。

IL编织器 --- Fody

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