关于Metalama使用Fabric操作项目或命名空间的问题

2022-04-25 09:33:19
目录
Metalama中的Fabric可以做什么使用Fabric为指定的方法添加Aspect使用Fabric添加代码分析使用TypeFabric为类型动态添加方法引用

Metalama中的Fabric可以做什么

Metalama是一个基于微软编译器Roslyn的元编程的库,可以解决我在开发中遇到的重复代码的问题。但是其实Metalama不止可以提供编译时的代码转换,更可以提供自定义代码分析、与IDE结合的自定义代码修复与代码重构功能 等功能。

经过面对文档的学习,发现Metalama可以做到很多非常神奇的事情。

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个缺点:

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

此时我们就可以使用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));        }    }}

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