Metalama简介4.使用Fabric操作项目或命名空间

使用基于Roslyn的编译时AOP框架来解决.NET项目的代码复用问题
Metalama简介1. 不止是一个.NET跨平台的编译时AOP框架
Metalama简介2.利用Aspect在编译时进行消除重复代码
Metalama简介3.自定义.NET项目中的代码分析

Metalama中的Fabric可以做什么

Fabric通过修改项目、命名空间、类型来达到一些效果,这引起修改包括:添加Aspect或添加代码分析

使用Fabric为指定的方法添加Aspect

前文中我们写过一个简单的Aspect:

public class LogAttribute : OverrideMethodAspect {     public override dynamic? OverrideMethod()     {         Console.WriteLine(meta.Target.Method.ToDisplayString() + " 开始运行.");         var result = meta.Proceed();         Console.WriteLine(meta.Target.Method.ToDisplayString() + " 结束运行.");         return result;     } } 

当我们使用它时,我们要在对应的方法上添加这个Attribute:

[Log] private static int Add(int a, int b) //... ... 

那么当我们有一个Aspect要在项目中大量使用时,在每个方法上添加这个Aspect当然是一种方法,但是这种方法有2个缺点:

  1. 包含大量的重复代码[Log]
  2. 对于原代码的入侵性太强

此时我们就可以使用Fabric为所有符合要求的方法添加指定的Aspect:

internal class Fabric : ProjectFabric {     // 这个是重写项目的Fabric中修改项目的方法     public override void AmendProject(IProjectAmender amender)     {         // 添加 LogAttribute 到符合规则的方法上         // 为名为 Add 且 private 的方法添加 LogAttribute         amender.WithTargetMembers(c =>                 c.Types.SelectMany(t => t.Methods)                        .Where(t =>                               t.Name == "Add" &&                               t.Accessibility == Metalama.Framework.Code.Accessibility.Private)             ).AddAspect(t => new LogAttribute());     } } 

这样就可以在不入侵现有代码的情况下为指定的方法添加Aspect

使用Fabric添加代码分析

上文中我们提到,我们可以通过Aspect为代码添加代码分析,当我们要将一个包含(且仅包含)代码分析的Aspect应用于一批代码时,当然我们可以按本文示例1中的方法,直接使用Fabric将包含代码分析的Aspect应用于指定代码。

但还有另外一种方法,我们可以直接在Fabric中定义应用于指定代码的代码分析。

下面示例,我们验证所有类中的私有字段必须符合 _camelCase,并且使用一个NamespaceFabric来实现:

namespace FabricCamelCaseDemo; class Fabric : NamespaceFabric {     private static readonly DiagnosticDefinition<string> _warning = new(  "DEMO04",  Severity.Warning,  "'{0}'必须使用驼峰命名法并以'_'开头");     // 这个是命名空间的Fabric中修改命名空间规则 的方法     public override void AmendNamespace(INamespaceAmender amender)     { 	    // 取所有非static 的private的字段,并添加代码分析         amender.WithTargetMembers(c =>                                     c.AllTypes.SelectMany(t=>t.Fields)                                     .Where(t => t.Accessibility == Accessibility.Private && !t.IsStatic                                     )                                  )             //preview 0.5.8之前为 RegisterFinalValidator             .Validate(this.FinalValidator);     }      private void FinalValidator(in DeclarationValidationContext context)     {         var fullname = context.Declaration.ToDisplayString();         var fieldName = fullname.Split('.').LastOrDefault();         if (fieldName!=null && (!fieldName.StartsWith("_") || !char.IsLower(fieldName[1])))         {             context.Diagnostics.Report(_warning.WithArguments(fieldName));         }     } } 

Metalama简介4.使用Fabric操作项目或命名空间

当然因为当前使用的是NamespaceFabric所以该规则只应用于当前命名空间如,我们如果在另外一个命名空间中定义一个违反规则的字段的话,并不会有警告。

namespace FabricCamelCase;  internal class OtherNamespace {     int count = 0;     int _total = 0;     public int Add()     {         count++;         _total++;         return count + _total;     } } 

使用TypeFabric为类型动态添加方法

开始前伪造一个需求,假设我有一个类AddUtils专门处理加法操作,它里面应该有从2个到15个参数的Add方法15个(当然我知道,可以使用params等方法实现,所以这里是个伪需求)。
最终效果为

public class AddUtils {     public int Add2(int x1, int x2)     {         var result = 0;         result += x1;         result += x2;         return 2;     }     public int Add3(int x1, int x2, int x3)     {         var result = 0;         result += x1;         result += x2;         result += x3;         return 3;     } 	// 以此类推... 下面省去若干方法 } 

那么我们可以用Metalama如此实现

using System.Reflection.Emit; using Metalama.Framework.Aspects; using Metalama.Framework.Fabrics;  public class AddUtils {     private class Fabric : TypeFabric     {         // 实现的方法体         [Template]         public int MethodTemplate()         {             var num = (int) meta.Tags["nums"]!;             var result = 0;             foreach (var targetParameter in meta.Target.Parameters)             {                 result += targetParameter.Value;             }              return num;         }          public override void AmendType(ITypeAmender amender)         {             for (var i = 2; i < 15; i++)             {                 // 生成一个方法                 var methodBuilder = amender.Advices.IntroduceMethod(                     amender.Type,                     nameof(this.MethodTemplate),                     tags: new TagDictionary { ["nums"] = i });                 // 方法名                 methodBuilder.Name = "Add" + i;                 // 添加参数                 for (int parameterIndex = 1; parameterIndex <= i; parameterIndex++)                 {                     methodBuilder.AddParameter($"x{parameterIndex}", typeof(int));                 }             }         }     } } 

引用

本章源代码:https://github.com/chsword/metalama-demo
Metalama官方文档: https://doc.metalama.net/
Metalama Nuget包: https://www.nuget.org/packages/Metalama.Framework/0.5.11-preview

发表评论

相关文章