• IL编织器 --- Fody


    介绍

    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>falseIsCodeGeneratorDisabled>
        <EventInvokerName>OnPropertyChangedEventInvokerName>
      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(() => 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

  • 相关阅读:
    许可分析 license分析 第十七章
    【大数据采集工具-gobblin】
    论文阅读之Syntax Encoding with Application in Authorship Attribution(2018)
    ChinaSkills技能大赛网络系统管理Debian模块(样题一)||Client配置
    希尔排序和直接插入排序代码对比
    营丘福稻品牌山东大米 国稻种芯·中国水稻节:淄博高青招牌
    【C】想知道结构体的大小?内存对齐了解一下
    好商品好内容好运营,图文免佣这些爆单技巧你会吗?
    你还不会写API文档吗
    视频制作不求人:批量添加滚动字幕的详细教程
  • 原文地址:https://www.cnblogs.com/pandefu/p/17775991.html