前面一篇文章介绍了Source Generator的基本用法,本文结合实例再对Fody编织器的创建进行介绍。
值得注意的是,Source Generator生成代码的方式更加简单,而Fody必须在IL中间代码的基础上进行修改,会更加繁琐,本文只关注如何创建编织器,重点不在编制IL中间代码的语法上。
需求描述
本文实例很简单,当为一个方法添加[HelloWorldAddin]
注解时,就在该方法执行之前输出Hello World.
。
创建编织器项目
创建一个netstandard2.0类库项目,添加必要的NuGet包:
创建一个继承自BaseModuleWeaver
的编织器,实现核心逻辑函数Execute()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 using Fody;using Mono.Cecil.Cil;using Mono.Cecil;using System;using System.Collections.Generic;using System.Linq;using System.Reflection;namespace HelloWorldAddin.Fody { public class ModuleWeaver : BaseModuleWeaver { public override void Execute () { foreach (var type in ModuleDefinition.Types) { foreach (var method in type.Methods) { var customerAttribute = method.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == nameof (HelloWorldAddinAttribute)); if (customerAttribute != null ) { ProcessMethod(method); } } } } public override IEnumerable<string > GetAssembliesForScanning () { yield return "mscorlib" ; yield return "System" ; } private MethodInfo _writeLineMethod => typeof (Console).GetMethod("WriteLine" , new Type[] { typeof (string ) }); private void ProcessMethod (MethodDefinition method ) { var processor = method.Body.GetILProcessor(); var current = method.Body.Instructions.First(); var first = Instruction.Create(OpCodes.Nop); processor.InsertBefore(current, first); current = first; foreach (var instruction in GetInstructions (method )) { processor.InsertAfter(current, instruction); current = instruction; } } private IEnumerable<Instruction> GetInstructions (MethodDefinition method ) { yield return Instruction.Create(OpCodes.Nop); yield return Instruction.Create(OpCodes.Ldstr, "Hello World." ); yield return Instruction.Create(OpCodes.Call, ModuleDefinition.ImportReference(_writeLineMethod)); } } }
创建注解定义库
1 2 3 4 5 6 using System;namespace HelloWorldAddin { [AttributeUsage(AttributeTargets.Method) ] public class HelloWorldAddinAttribute : Attribute { } }
创建测试项目
如果要使用.NET Framework框架,必须使用SDK风格的项目,添加Fody的NuGet包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <Project Sdk ="Microsoft.NET.Sdk" > <PropertyGroup > <OutputType > Exe</OutputType > <TargetFramework > net48</TargetFramework > <ImplicitUsings > enable</ImplicitUsings > <Nullable > enable</Nullable > <LangVersion > preview</LangVersion > </PropertyGroup > <ItemGroup > <PackageReference Include ="Fody" Version ="6.9.2" > <PrivateAssets > all</PrivateAssets > <IncludeAssets > runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets > </PackageReference > </ItemGroup > <ItemGroup > <ProjectReference Include ="..\HelloWorldAddin\HelloWorldAddin.csproj" /> <WeaverFiles Include ="$(SolutionDir)HelloWorldAddin.Fody\bin\$(Configuration)\netstandard2.0\HelloWorldAddin.Fody.dll" /> </ItemGroup > </Project >
1 2 3 <Weavers xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation ="FodyWeavers.xsd" > <HelloWorldAddin /> </Weavers >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 using HelloWorldAddin;namespace TestHelloWorldAddin { internal class Program { static void Main (string [] args ) { Echo(); } [HelloWorldAddin ] public static void Echo () { Console.WriteLine("Hello Fody." ); } } }