使用基于Roslyn的编译时AOP框架来解决.NET项目的代码复用问题
Metalama简介1. 不止是一个.NET跨平台的编译时AOP框架
Metalama简介2.利用Aspect在编译时进行消除重复代码
在Visual Studio
中有提供快速操作
(小灯泡)功能
以及重构
(小刷子)功能
使用它们可以快速进行一些快捷的针对代码的操作,如提取接口、添加实现、自动属性、快速重构、删除引用等。
除官方提供的功能外我们还可以使用很多第三方插件来支持更多地功能。
Metalama
可以通过编写代码的形式,让我们为指定的代码添加重构
或快速操作
的功能。
很多图形编程或游戏编程中,我们会用到各种自定义类如矩阵、复数、坐标系等,为了方便Debug,我们通常会为这些类增加一个ToString
方法的重写。
例如
internal class Program
{
private static void Main()
{
var point = new Point { X = 5, Y = 3};
Console.WriteLine($"point = {point}");
}
}
internal class Point
{
public double X;
public double Y;
public override string ToString()
{
return $"({X}, {Y})";
}
}
如果我们不想手写这个ToString
方法,而想让VS直接为它生成。
则我们可以使用Metalama
定义一个LiveTemplate,这样就可以在VS的工具中使用它了。
[LiveTemplate] // 表示当前Aspect为VS添加LiveTempate
internal class ToStringAttribute : TypeAspect
{
[Introduce(WhenExists = OverrideStrategy.Override, Name = "ToString")]
public string IntroducedToString()
{
var stringBuilder = new InterpolatedStringBuilder();
stringBuilder.AddText("{ ");
stringBuilder.AddText(meta.Target.Type.Name);
stringBuilder.AddText(" ");
var fields = meta.Target.Type.FieldsAndProperties.Where(f => !f.IsStatic).ToList();
var i = meta.CompileTime(0);
foreach (var field in fields)
{
if (i > 0)
{
stringBuilder.AddText(", ");
}
stringBuilder.AddText(field.Name);
stringBuilder.AddText("=");
stringBuilder.AddExpression(field.Invokers.Final.GetValue(meta.This));
i++;
}
stringBuilder.AddText(" }");
return stringBuilder.ToValue();
}
}
这样在,下列代码中使用重构
功能,即可看到Metalama
给的实时代码提示。
internal class Point
{
public double X;
public double Y;
}
我们最终的目的如下,对于标注了[Tostring]
的类,增加一个将[ToString]切换至手动实现
的功能点击后可实现自动添加一个ToString:
这需要我们在Aspect``ToStringAttribute
中添加一个提示:
public class ToStringAttribute : TypeAspect
{
public override void BuildAspect(IAspectBuilder<INamedType> builder)
{
base.BuildAspect(builder);
// 添加一个建议手动实现的重构提示
if (builder.AspectInstance.Predecessors[0].Instance is IAttribute attribute)
{
builder.Diagnostics.Suggest(
new CodeFix("将 [ToString] 切换至手动实现", codeFixBuilder => this.ImplementManually(codeFixBuilder, builder.Target)),
builder.Target);
}
}
/// <summary>
/// 当点击手动实现时的操作
/// </summary>
private async Task ImplementManually(ICodeActionBuilder builder, INamedType targetType)
{
await builder.ApplyAspectAsync(targetType, this);
await builder.RemoveAttributesAsync(targetType, typeof(ToStringAttribute));
}
[Introduce(WhenExists = OverrideStrategy.Override, Name = "ToString")]
public string IntroducedToString()
{
// 获取非静态字段
var fields = meta.Target.Type.FieldsAndProperties.Where(f => !f.IsStatic).ToList();
// 构建一个$""字符串
var stringBuilder = new InterpolatedStringBuilder();
stringBuilder.AddText("{ ");
stringBuilder.AddText(meta.Target.Type.Name);
stringBuilder.AddText(" ");
var i = meta.CompileTime(0);
foreach (var field in fields)
{
if (i > 0)
{
stringBuilder.AddText(", ");
}
stringBuilder.AddText(field.Name);
stringBuilder.AddText("=");
stringBuilder.AddExpression(field.Invokers.Final.GetValue(meta.This));
i++;
}
stringBuilder.AddText(" }");
return stringBuilder.ToValue();
}
}
这样就可以对于已经添加了[ToString]
的类实现以上功能
[ToString]
internal class Point // 在此处触发 Ctrl+.或右键
{
public double X;
public double Y;
}
本章源代码:https://github.com/chsword/metalama-demo
Metalama官方文档: https://doc.metalama.net/
Metalama Nuget包: https://www.nuget.org/packages/Metalama.Framework/0.5.13-preview