理想的代码优化方式

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

方式 描述 应用时可能产生的问题
硬编码 多数新手,或逐渐腐坏的项目会这么干,会直接复制之前实现的代码 带来的问题显而易见的多,例如架构会逐渐随时间被侵蚀,例外越来越多
提取函数 提取成为函数,然后复用 提取函数,然后复用,会比直接硬编码好些,但是仍然存在大量因“例外”而导致增加参数、增加函数重载的情况
模板生成器 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. android编译时注解,Android编译时注解框架系列2-Run Demo

    概述 先讲一下编写<Android编译时注解框架>的初衷吧,APT其实并不难,可以说是简单且高效,但关于APT的资料却并不多,甚至很多人都不知道这么一个技术.国内关于APT的博客屈指可数, ...

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

    maven常见问题处理(3-3)Gradle编译时下载依赖失败解决方法 参考文章: (1)maven常见问题处理(3-3)Gradle编译时下载依赖失败解决方法 (2)https://www.cnbl ...

  3. eclipsejvm内存不足_Eclipse无法调试及编译时内存不足的解决

    Eclipse下Debug时,弹出错误提示:"Unable to install breakpoint due to missing line number attributes. Modi ...

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

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

  5. 分享:新入职时,如何快速熟悉一个项目的代码

    一.总体思路 昨晚是深夜撰文的阿菌,希望通过这篇文章和大家分享一下,初入职场时,如何才能快速地熟悉一个项目的代码. 说实话,感觉自己去年入职时上手项目的速度是比较慢的,可能是没有一些系统的方法论参考吧 ...

  6. 【ubuntu编译时内存不足的解决的办法】

    设置交换空间的大小 sudo swapoff -a // 2G sudo dd if=/dev/zero of=/swapfile bs=64M count=32 // 4G sudo dd if=/ ...

  7. 颠覆C#王权的“魔比斯环” — 实现AOP框架的终极利器

    本文为原创,如需转载,请注明作者和出处,谢谢! 时间要追溯到2005年.那时正在做硕士论文.题目是"AOP framework for .net".这个AOP框架将使用C#2.0来 ...

  8. MSBuild + MSILInect实现编译时AOP-改变前后对比

    实现静态AOP,就需要我们在预编译时期,修改IL实现对代码逻辑的修改.Mono.Cecil就是一个很好的IL解析和注入框架,参见编译时MSIL注入--实践Mono Cecil(1). 我的思路为:在编 ...

  9. 手动编译 lombok_Lombok,一种编译时Java注释预处理器,可最大程度地减少代码大小...

    手动编译 lombok 在本文中,我们将看到如何在常规Java代码中使用lombok来最大程度地减少代码长度和冗余. 什么是Lombok? Lombok,一个编译时注释预处理器,有助于在编译时注入一些 ...

最新文章

  1. opencv获取摄像头帧率分辨率
  2. .net2.0中SqlBulkCopy批量复制数据出错原因分析!
  3. 2017-2018-2 20155303『网络对抗技术』Final:Web渗透获取WebShell权限
  4. python协程库_python中协程的详解(附示例)
  5. 学习笔记总结(VC)
  6. 递归过程中语句执行顺序
  7. comparator比较器用法_汽车三元催化器堵塞咋办?不拆不换,用这招清理干净、动力猛如虎...
  8. 国防科技大学计算机学院教员,我国最好的四所科技大学,第一名排名世界前列,还有一所是铁饭碗...
  9. UI必不可少!手机计算器界面设计可学习案例
  10. C++STL笔记(一):STL综述
  11. 【第一部分】04Leetcode刷题
  12. 如何避免_如何避免钢板弹簧受损
  13. Matlab定义自定义深度学习网络中间层
  14. App测试查看日志(详细)
  15. 电脑加域、exchange邮箱设置、绑定打印机
  16. 区块链开发金融交易平台
  17. latex中脚注内容不显示
  18. linux 活动主分区,主引导扇区(MBR),分区表(DPT)及活动分区(DBR) | 技术部落
  19. 【本周最新】qlv转mp4格式转换器 工具 软件
  20. bzoj 2563阿狸和桃子的游戏

热门文章

  1. Oracle 中UNDO与REDO的差别具体解释
  2. Spring工具类的使用
  3. 《深入理解Hadoop(原书第2版)》——2.6本章小结
  4. Cocos2d-JS v3.0 alpha
  5. Linux查看硬件信息很Easy
  6. Accoridion折叠面板
  7. nowcoder OI 周赛 最后的晚餐(dinner) 解题报告
  8. Linux 用户名、主机添加背景色
  9. (php)thinkphp3.2配置sql_server
  10. JDBC学习笔记之JDBC简介