尚斌 发表于 2023-10-19 23:50:44

IL编织器 --- Fody

介绍


这个项目的名称“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 URLFody编织.net程序集的可扩展工具https://github.com/Fody/FodyAutoProperties.Fody这个外接程序为您提供了对自动属性的扩展控制,比如直接访问backing字段或拦截getter和setter。https://github.com/tom-englert/AutoProperties.FodyPropertyChanged.Fody将属性通知添加到实现INotifyPropertyChanged的所有类。https://github.com/Fody/PropertyChangedInlineIL.Fody在编译时注入任意IL代码。https://github.com/ltrzesniewski/InlineIL.FodyMethodDecorator.Fody通过IL重写编译时间装饰器模式https://github.com/Fody/MethodDecoratorNullGuard.Fody将空参数检查添加到程序集https://github.com/Fody/NullGuardToString.Fody给属性生成ToString()方法https://github.com/Fody/ToStringRougamo.Fody在编译时生效的AOP组件,类似于PostSharp。https://github.com/inversionhourglass/RougamoAutoProperties.Fody

这个插件提供了对自动属性的扩展控制,比如直接访问backing字段或拦截getter和setter。
using System;
using AutoProperties;
using Xunit;

public class AutoPropertiesInterceptor
{
   
    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);
    }

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

   
    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");
    }

   
    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( string propertyName = null)
    {
      numberOfPropertyChangedCalls += 1;

      PropertyChanged?.Invoke(this, new(propertyName));
    }
}除此之外,该插件附带了一个 C# 代码生成器,只需将实现 INotifyPropertyChanged 接口或包含 属性的类标记为 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
{
   
    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;


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
{
   
    public static void MyMethod()
    {

    }

   
    public static void MyExceptionMethod()
    {
      throw new("Foo");
    }
}示例:
using Xunit;

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

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

该插件向程序集添加null参数检查,支持三种操作模式:隐式模式、显式模式和可为空引用类型模式。

[*]在隐式模式下,假定一切都不为空,除非标记为 。这是 NullGuard 一直以来的工作方式。
[*]在显式模式下,假定一切都可为空,除非标记为 。这种模式旨在支持 ReSharper(R#)的可为空性分析,使用悲观模式。
[*]在可为空引用类型模式下,使用 C# 8 可为空引用类型(NRT)注释来确定类型是否可为空。
如果没有显式配置,NullGuard 将按以下方式自动检测模式:

[*]如果检测到 C# 8 可为空属性,则使用可为空引用类型模式。
[*]引用 JetBrains.Annotations 并在任何地方使用 将切换到显式模式。
[*]如果不满足上述条件,则默认为隐式模式。
示例:
using Xunit;

public class NullGuardSample
{
   
    public void Run()
    {
      var targetClass = new TargetClass();
      Assert.Throws<ArgumentNullException>(() => targetClass.Method(null));
    }
}

public class TargetClass
{
    public void Method(string param)
    {
    }
}ToString.Fody

该插件可以从带有属性修饰的类的公共属性中生成ToString方法。
using System.Diagnostics;
using Xunit;

public class ToStringSample
{
   
    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());
    }
}


class Person
{
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }

   
    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 />
<ConfigureAwaitContinueOnCapturedContext="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

来源:https://www.cnblogs.com/pandefu/archive/2023/10/19/17775991.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: IL编织器 --- Fody