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

    image

    当然因为当前使用的是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

  • 相关阅读:
    Excel多线程导入数据库
    spring5.0源码解析 Aop 02
    const修饰指针
    捷报连连!怿星科技荣获北京市科学技术进步奖一等奖
    oracle rman restore database的时候报错RMAN-06023: 没有找到数据文件1的副本来还原
    C++11范围for
    基于springcloud+vue的分布式架构网上商城网站 前后端分离
    计算二值化图片的迭代次数
    python制作小游戏之二2048第二部分
    RabbitMQ的5种模式——再探RabbitMQ的模式,简单、工作,发布订阅(广播),路由、主题 & 页面分析
  • 原文地址:https://www.cnblogs.com/chsword/p/metalama_4.html