理想的代码优化方式

团队日常协作中,自然而然的会出现很多重复代码,根据这些代码的种类,之前可能会以以下方式处理

方式 描述 应用时可能产生的问题
硬编码 多数新手,或逐渐腐坏的项目会这么干,会直接复制之前实现的代码 带来的问题显而易见的多,例如架构会逐渐随时间被侵蚀,例外越来越多
提取函数 提取成为函数,然后复用 提取函数,然后复用,会比直接硬编码好些,但是仍然存在大量因“例外”而导致增加参数、增加函数重载的情况
模板生成器 CodeSmith/T4等 因为是独立进程,所以对于读取用户代码或项目,实现难度较高,且需要现有用户项目先生成成功,再进行生成 ,或者是完全基于新项目
代码片段 VS自带的代码片段功能 无法对复杂的环境或条件做出响应
AOP框架 面向切面编程,可以解决很多于用户代码前后增加操作的事情 但是大多AOP框架都是基于透明代理形式实现的,对于相互调用较多的代码,但形成性能压力,而且因为要符合透明代理的规则,所以要提供相应的子类或接口。

基于Rosyln的编译时插入代码

但以上这几种,AOP算是最理想的方式,但是感觉上还可以有更好的解决方案。

直到读到了这篇文章文章 Introducing C# Source Generators,文中提供了一种新的解决方案,即通过RoslynSource Generator在编译时直接读取当前项目中的语法树,处理并生成的新代码,然后在编译时也使用这些新代码。

那么如果可以读取现有代码的语法树,通过读取代码中的标记,那么在代码生成过程中是否就能直接生成。

实现如下效果:

项目中的源代码 Program.cs

internal class Program
{
[Log]
private static int Add( int a, int b )
{
return a + b;
}
}

自动根据 LogAttribute 自动编译成的代码 Program.g.cs

internal class Program
{
[Log]
private static int Add( int a, int b )
{
Console.WriteLine("Program.Add(int, int) 开始运行.");
int result;
result = a + b;
Console.WriteLine("Program.Add(int, int) 结束运行.");
return result;
}
}

当然LogAttribute中需要去实现插入代码。

然后项目自动使用新生成的Program.g.cs进行编译。这样就实现了基于编译时的AOP。

即实现以下流程

使用Metalama实现以上流程

经过寻找,发现其实已经有框架可以实现我上面说的流程了,也就是在编译时实现代码的插入。

https://www.postsharp.net/metalama

下面作一个简单示例

  1. 创建一个.NET6.0的控制台应用,我这里命名为LogDemo,

    其中的入口文件Program.cs
namespace LogDemo {
public class Program
{
public static void Main(string[] args)
{
var r = Add(1, 2);
Console.WriteLine(r);
}
// 这里写一个简单的方法,一会对这个方法进行代码的插入
private static int Add(int a, int b)
{
var result = a + b;
Console.WriteLine("Add" + result);
return result;
}
}
}
  1. 在项目中使用Metalama

通过引用包 https://www.nuget.org/packages/Metalama.Framework, 注意Metalama当前是Preview版本,如果通过可视化Nuget管理器引入,需要注意勾选包含预发行版

dotnet add package Metalama.Framework --version 0.5.7-preview
  1. 编写一个AOP的Attribute

在项目中引入 Metalama.Framework后无需多余配置或代码,直接编写一个AOP的Attribute

using Metalama.Framework.Aspects;

namespace LogDemo {
public class Program
{
public static void Main(string[] args)
{
var r = Add(1, 2);
Console.WriteLine(r);
}
// 在这个方法中使用了下面的Attribute
[LogAttribute]
private static int Add(int a, int b)
{
var result = a + b;
Console.WriteLine("Add" + result);
return result;
}
}
// 这里是增加的 Attribute
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; }
}
}
  1. 执行结果如下
Program.Add(int, int) 开始运行.
Add3
Program.Add(int, int) 结束运行.
3
  1. 生成的程序集进行反编译,得到的代码如下:
using Metalama.Framework.Aspects;
namespace LogDemo {
public class Program
{
public static void Main(string[] args)
{
var r = Add(1, 2);
Console.WriteLine(r);
}
// 在这个方法中使用了下面的Attribute
[LogAttribute]
private static int Add(int a, int b)
{
Console.WriteLine("Program.Add(int, int) 开始运行.");
int result_1;
var result = a + b;
Console.WriteLine("Add" + result);
result_1 = result;
Console.WriteLine("Program.Add(int, int) 结束运行.");
return result_1;
}
}
#pragma warning disable CS0067
// 这里是增加的 Attribute
public class LogAttribute : OverrideMethodAspect
{
public override dynamic? OverrideMethod() => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");
}
#pragma warning restore CS0067
}

总结

这样就完全实现了我之前想要的效果,当然使用Metalama还可以实现很多能极大地提高生产力的功能,它不仅可以对方法进行改写,也可以对属性、字段、事件、甚至是类、命名空间进行一些操作 。

引用

Introducing C# Source Generators:https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/

Metalama官网:https://www.postsharp.net/metalama

使用基于Roslyn的编译时AOP框架来解决.NET项目的代码复用问题的更多相关文章

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

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

  2. java 编译时注解框架 lombok-ex

    lombok-ex lombok-ex 是一款类似于 lombok 的编译时注解框架. 编译时注,拥有运行时注解的便利性,和无任何损失的性能. 主要补充一些 lombok 没有实现,且自己会用到的常见 ...

  3. 在Linux下安装PHP过程中,编译时出现错误的解决办法

    在Linux下安装PHP过程中,编译时出现configure: error: libjpeg.(a|so) not found 错误的解决办法 configure: error: libjpeg.(a ...

  4. maven常见问题处理(3-3)Gradle编译时下载依赖失败解决方法

    Gradle编译时在本地仓库中如果没有发现依赖,就会从远程仓库中下载, 默认的远程仓库为 mavenCentral(),即 http://repo1.maven.org/maven2/往往访问速度特别 ...

  5. Ubuntu下math库函数编译时未定义问题的解决

    自己在Ubuntu下练习C程序时,用到了库函数math.h,虽然在源程序中已添加头文件“math.h”,但仍提示所用函数未定义,原本以为是程序出错了,找了好久,这是怎么回事呢? 后来上网查了下,发现是 ...

  6. Sass编译时Invalid US-ASCII character解决办法

    编译scss文件时,如果出现如下错误 Error: Invalid US-ASCII character "\xC2" on line 63 of src/assets/_scss ...

  7. oracle‘s package,function,proceture编译时无响应(解决)

    在对Procedure.Function或Package进行Debug时,如果长时间没有操作,公司的防火墙会杀掉会话连接.这个时候数据库不会主动的释放会话的资源,如果再次对Procedure.Func ...

  8. Gradle编译时下载依赖失败解决方法

    如果Gradle在编译的时候没有在本地仓库中发现依赖,就会从远程仓库中下载,默认的远程仓库为mavenCentral(),也就是http://repo1.maven.org/maven2/,但是往往访 ...

  9. 编译时bad substitution的解决办法

    由于使用的使用的编译器不同导致, 需要使用shell为 #!/bin/bash 即可.

随机推荐

  1. ArcGIS拓扑小技巧:两个面矢量合并但不叠加

    已知数据:底图图斑A,更新图斑B 使用软件:ArcMap 要求:将B于A合并为一个图斑.A与B不能重叠,重叠处以A为基准切割B图斑. 下面开始操作: 1. 将数据集中的图斑A.B添加到数据框内   打 ...

  2. nginx+keepalived 高可用方案

    nginx+keepalived 高可用方案 准备工作 192.168.157.11 192.168.157.12 安装nginx 跟新yum源文件 rpm -ivh http://nginx.org ...

  3. 4月25日 python学习总结 互斥锁 IPC通信 和 生产者消费者模型

    一.守护进程 import random import time from multiprocessing import Process def task(): print('name: egon') ...

  4. IIS将应用程序池配置为在计划时间执行回收 (IIS 7)

    将应用程序池配置为在计划时间执行回收 您可以通过以下方法执行此过程:使用用户界面 (UI).在命令行窗口中运行 Appcmd.exe 命令.直接编辑配置文件或编写 WMI 脚本. 如下只介绍用户界面U ...

  5. SpringCloudAlibaba 微服务讲解(一)微服务介绍

    微服务介绍 1.1 系统架构的演变 随若互联网的发展,网站应用的规模也在不断的扩大,逬而导致系统架构也在不断的进行变化.从互联 网早起到现在,系统架构大体经历了下面几个过程:单体应用架构一蟻直应用架构 ...

  6. 女朋友汇总表格弄了大半天,我实在看不下去了,用40行代码解决问题 | Python使用openpyxl库读写表格Excel(xlsx)

    1.openpyxl基本操作 python程序从excel文件中读数据基本遵循以下步骤: 1.import openpyxl 2.调用openpyxl模块下的load_workbook('你的文件名. ...

  7. 什么是LSA,在OSPF中LSA是什么

    什么是LSA:链路状态通告,它存在于LSU(链路状态更新包) Type 1 LSA:路由器LSA       每个OSPF路由器都会产生路由器LSA,描述了对应设备的物理接口所连接的链路和接口,并指明 ...

  8. 学习廖雪峰的Git教程3--从远程库克隆以及分支管理

    一.远程库克隆 这个就比较简单了, git clone git@github.com:****/Cyber-security.git 远程库的地址可以在仓库里一个clone or download的绿 ...

  9. Spring 应用程序有哪些不同组件?

    Spring 应用一般有以下组件:接口 - 定义功能.Bean 类 - 它包含属性,setter 和 getter 方法,函数等.Spring 面向切面编程(AOP) - 提供面向切面编程的功能.Be ...

  10. Flask-Migrate使用教程

    功能:flask-migrate是flask的一个扩展模块,主要是扩展数据库表结构的. 项目准备:一个干净的Flask项目,下载连接地址: https://pan.baidu.com/s/1WqdIN ...