使用基于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));
}
}
}

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

Metalama简介4.使用Fabric操作项目或命名空间的更多相关文章

  1. Metalama简介5.配合VisualStudio自定义重构或快速操作功能

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

  2. Metalama简介3.自定义.NET项目中的代码分析

    本系列其它文章 使用基于Roslyn的编译时AOP框架来解决.NET项目的代码复用问题 Metalama简介1. 不止是一个.NET跨平台的编译时AOP框架 Metalama简介2.利用Aspect在 ...

  3. Metalama简介1. 不止是一个.NET跨平台的编译时AOP框架

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

  4. Metalama简介2.利用Aspect在编译时进行消除重复代码

    上文介绍到Aspect是Metalama的核心概念,它本质上是一个编译时的AOP切片.下面我们就来系统说明一下Metalama中的Aspect. Metalama简介1. 不止是一个.NET跨平台的编 ...

  5. C#操作项目配置文件

    前言 对于项目配置文件的读取和修改,.net 提供了ConfigurationManager(位于System.Configuration命名空间) 和WebConfigurationManager( ...

  6. 【python】-- MySQL简介、安装、操作

    MySQL简介.安装.操作 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,每个数据库都有一个或多个不同的API用于创建,访问,管理,搜索和复制所保存的数据.我们也可以将数据存储 ...

  7. 爬虫简介与excel表格操作

    爬虫简介与excel表格操作 re模块简介 1.在python中使用正则表达式的话那么re模块就是选择之一 import re # 导入re模块 2.在re模块中使用findall找到所有我们给他的值 ...

  8. OpenStack/Gnocchi简介——时间序列数据聚合操作提前计算并存储起来,先算后取的理念

    先看下 http://www.cnblogs.com/bonelee/p/6236962.html 这里对于环形数据库的介绍,便于理解归档这个操作! 转自:http://blog.sina.com.c ...

  9. web框架开发-Django模型层(1)之ORM简介和单表操作

    ORM简介 不需要使用pymysql的硬编码方式,在py文件中写sql语句,提供更简便,更上层的接口,数据迁移方便(有转换的引擎,方便迁移到不同的数据库平台)…(很多优点),缺点,因为多了转换环节,效 ...

随机推荐

  1. python爬虫 Selenium库学习

    一.自动化测试工具,支持多种浏览器,解决JS渲染问题 二.安装 pip3 install Selenium 三.操作介绍(因为是学习别人的课程为了尊重知识产权,部分代码就不显示了) 1驱动浏览器 br ...

  2. java实现稀疏矩阵的压缩与解压

    任务要求 把棋盘当作一个稀疏矩阵,0表示没棋,1表示黑棋,2表示蓝棋. 把该稀疏矩阵压缩以三元组形式表示并以文件形式保存,再写另一个程序读取文件中的信息把压缩后的三元组还原成原来的稀疏矩阵. 其中三元 ...

  3. 麒麟系统开发笔记(二):国产麒麟系统搭建Qt开发环境安装Qt5.12

    前言   开发国产应用,使用到银河麒麟V4,V10,本篇以V10记录,参照上一篇可安装V4.V7.V10三个版本,麒麟V4系自带了Qt,麒麟V10没有自带Qt,需要自己编译搭建环境.   银河麒麟V1 ...

  4. List集合工具类之"将list集合按"指定长度"进行切分Lists.partition和ListUtils.partition"

    将list集合按"指定长度"进行切分,返回新的List<List<类型>>集合,如下的:  方法1:List<List<Integer>& ...

  5. USB与电池切换电路图

  6. 对height 100%和inherit的总结

    对height 100%和inherit的总结 欢迎大家来我的博客留言:https://sxq222.github.io/CSS%...博客主页:https://sxq222.github.io 正文 ...

  7. java中this这个概念初学者非常难理解,请举例说明

    4.this关键字(this key word) 继上一小节,(3.一个对象可能有多个参考)this是当中的一个参考!指向他自己. class MyTestDate {    int year;    ...

  8. 如何在jsp界面进行判断再输出不同的值

    C标签的out <td> <c:if test="${nowtime eq returntime}"> <c:out value="逾期&q ...

  9. python的编译和解释

    编译和解释 1.编译: 将源代码一次性转换成目标代码的过程 源代码 → 编辑器 →目标代码 →程序执行(同时程序输入)→结果输出 2.解释: 将源代码逐条转换成目标代码同时逐条运行的过程 源代码+程序 ...

  10. JAVASE for 笔记

    //0到100中奇数偶数的和package com.huang.boke.flowPath;public class Fordeme { public static void main(String[ ...